
日记
上午
考试
T1
首先,想了一个解法,口胡了一下,假了。
然后又想了一个解法,口胡了一下,又假了。
然后也是思考良久,没想出来。
T2
先看特殊性质 :全是 A
。然后我们口胡一下,发现,如果设变化前的 A
的数量为 ,变化之后为 ,那么有 ,于是我们便拿到了 分。
然后又去打了一下特殊性质 ,挂了。
但是,我打完俩特殊性质,脑袋一热,去写 T3 了。
T3
也是先把暴力打出来了,然后又打了一个比暴力跑的还慢的数位 dp。不出所料,只得了 分。
总共也是只得了 分,本来应该拿 的,因为 T2 是一道黄题,我个糖人,没写出来。具体有多糖呢,请听下回分解。
下午
上午先把 T2 改出来了,现在该讲讲有多糖了。
T2
因为如果一个 A
要变成 B
的话,只可能变成 ,对 取模全都是 。于是我们可以把 和 里的 看成 , 看成 。然后,每次查询他们的区间和是否在模 的意义下同余就行了。
T1
其实第一个想法是半对的,独立计算每一个点的贡献。记录每一个点左上有多少点,右下有多少点,那么就会有他们乘积数量的贡献。但是正解的话还要加一个容斥。
但是还有一个叫做二维数点的东西,听都没听过。
代码:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
signed main()
{
init();
scanf("%lld",&n);
for (int i = 1; i <= n; i++)
scanf("%lld%lld",&a[i].first,&a[i].second);
sort(a + 1,a + n + 1);
for (int i = 1; i <= n; i++)
y[i] = a[i].second;
//以下4行是离散化
sort(y + 1,y + n + 1);
m = unique(y + 1,y + n + 1) - y - 1;
for (int i = 1; i <= n; i++)
a[i].second = lower_bound(y + 1,y + m + 1,a[i].second) - y;
//以下六行是统计有多少在点i左边的
for (int i = 1; i <= n; i++)
{
pre[i].first = que(a[i].second);
pre[i].second = i - 1 - pre[i].first;
upd(a[i].second,1);
}
memset(tree,0,sizeof tree);
//以下六行是统计有多少在点i右边的
for (int i = n; i >= 1; i--)
{
suf[i].first = que(a[i].second);
suf[i].second = n - i - suf[i].first;
upd(a[i].second,1);
}
//以下12行是容斥
int ans = (p2[n] - 1) * n % mod;
for (int i = 1; i <= n; i++)
{
ans = (ans + p2[pre[i].first]) % mod;
ans = (ans + p2[pre[i].second]) % mod;
ans = (ans + p2[suf[i].first]) % mod;
ans = (ans + p2[suf[i].second]) % mod;
ans = ((ans - p2[pre[i].first + suf[i].first]) % mod + mod) % mod;
ans = ((ans - p2[pre[i].second + suf[i].second]) % mod + mod) % mod;
ans = ((ans - p2[pre[i].first + pre[i].second]) % mod + mod) % mod;
ans = ((ans - p2[suf[i].second + suf[i].first]) % mod + mod) % mod;
}
printf("%lld",ans);
return 0;
}
T4
先考虑朴素的 dp 方程:设 表示考虑到第 个矩阵,前面都合法的方案数。还有 ,那么有转移方程:
当然,还有一个必不可少的限制条件:。
我们把 数组换成函数的形式:,那么很显然,它是一个下凸的函数。
那有人要问了,为啥呢?具体来说, 是一个值为 的常值函数,而 会在它上面加一个绝对值函数,而绝对值函数是下凸的,那么 肯定就是下凸的了。 又会在 上再加一个绝对值函数,俩下凸加起来也是下凸的。
那又有人要问了,为啥俩下凸加起来也是下凸的呢?因为下凸的定义是二阶导恒为正,如果你把俩函数加起来,他们的一阶导相加了,二阶导也就相加了。两个正数又加不出来负数。
根据 zxy
教我们的二次函数求最值方法(这里要推广到凸函数),设这个函数在 的范围内斜率为 。那么我们直接开始分类讨论:
先看大括号,那么发现,每次就相当于把 从某一个 的地方分开,左边往左移 ,右边往右移 , 变成 , 变成 ,对于这个偏移,我们用两个变量记录就行了。
现在我们来看加入绝对值的情况。加入绝对值肯定会让 左右两侧的斜率发生变化,也就是:左边的斜率 ,右边的 。然后,我们又来分类讨论啦。
- 在中间,那么加入之后, 和 肯定就是 了。
- 在左边,那么现在的 和 在哪里呢?
我们看,因为右边要 ,那么右边原本斜率为 的就变成了中间 ,而 左边就是斜率变成 。 - 右边同理。
然后,我们就可以记录每一个在左或者右的拐点,用堆维护,每次就可以取到中间的 ,然后再进行操作。
最后一个问题:如何统计答案?
设 表示 中间那段 的纵坐标。
- 在中间,
- 在左边,那么有 ,那么有
- 右边同理,有
最后放代码:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
#include<bits/extc++.h>
#define int long long
using namespace std;
const int maxn = 1e5 + 5;
int n;
int l[maxn],r[maxn],len[maxn];
priority_queue<int,vector<int>,less<int>>ql;
priority_queue<int,vector<int>,greater<int>>qr;
signed main()
{
scanf("%lld",&n);
for (int i = 1; i <= n; i++)
{
scanf("%lld%lld",l + i,r + i);
len[i] = r[i] - l[i];
}
//最开始的拐点只有l[1]
ql.push(l[1]);
qr.push(l[1]);
int ans = 0;
for (int i = 2; i <= n; i++)
{
static int pl = 0,pr = 0;
//记录偏移量
pl -= len[i];
pr += len[i - 1];
//计算tmpl,tmpr
int tmpl = ql.top() + pl;
int tmpr = qr.top() + pr;
if (l[i] < tmpl)//在左边的情况
{
ans += tmpl - l[i];
ql.pop();//原来的拐点不对了
//因为左边的斜率-1,右边的斜率+1,那么两边的斜率就会相差2
//在堆里再扔一个长度为0的线段,表现在代码里就是push两次
//这样就可以保证每条相邻的线斜率相差1
ql.push(l[i] - pl);
ql.push(l[i] - pl);
qr.push(tmpl - pr);
}
else if (l[i] > tmpr)
{
//同上
ans += l[i] - tmpr;
qr.pop();
qr.push(l[i] - pr);
qr.push(l[i] - pr);
ql.push(tmpr - pl);
}
else
{
//ans不变
ql.push(l[i] - pl);
qr.push(l[i] - pr);
}
}
printf("%lld",ans);
return 0;
}
于是,颓废的一天就这么的过完了。
希望下次模拟赛能够不要切不出糖题。