上午
考试
T1
首先,想了一个解法,口胡了一下,假了。
然后又想了一个解法,口胡了一下,又假了。
然后也是思考良久,没想出来。
T2
先看特殊性质 :全是 A。然后我们口胡一下,发现,如果设变化前的 A 的数量为 ,变化之后为 ,那么有 ,于是我们便拿到了 分。
然后又去打了一下特殊性质 ,挂了。
但是,我打完俩特殊性质,脑袋一热,去写 T3 了。
T3
也是先把暴力打出来了,然后又打了一个比暴力跑的还慢的数位 dp。不出所料,只得了 分。
总共也是只得了 分,本来应该拿 的,因为 T2 是一道黄题,我个糖人,没写出来。具体有多糖呢,请听下回分解。
下午
上午先把 T2 改出来了,现在该讲讲有多糖了。
T2
因为如果一个 A 要变成 B 的话,只可能变成 ,对 取模全都是 。于是我们可以把 和 里的 看成 , 看成 。然后,每次查询他们的区间和是否在模 的意义下同余就行了。
T1
其实第一个想法是半对的,独立计算每一个点的贡献。记录每一个点左上有多少点,右下有多少点,那么就会有他们乘积数量的贡献。但是正解的话还要加一个容斥。
但是还有一个叫做二维数点的东西,听都没听过。
代码:
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 教我们的二次函数求最值方法(这里要推广到凸函数),设这个函数在 的范围内斜率为 。那么我们直接开始分类讨论:
先看大括号,那么发现,每次就相当于把 从某一个 的地方分开,左边往左移 ,右边往右移 , 变成 , 变成 ,对于这个偏移,我们用两个变量记录就行了。
现在我们来看加入绝对值的情况。加入绝对值肯定会让 左右两侧的斜率发生变化,也就是:左边的斜率 ,右边的 。然后,我们又来分类讨论啦。
- 在中间,那么加入之后, 和 肯定就是 了。
- 在左边,那么现在的 和 在哪里呢?
我们看,因为右边要 ,那么右边原本斜率为 的就变成了中间 ,而 左边就是斜率变成 。 - 右边同理。
然后,我们就可以记录每一个在左或者右的拐点,用堆维护,每次就可以取到中间的 ,然后再进行操作。
最后一个问题:如何统计答案?
设 表示 中间那段 的纵坐标。
- 在中间,
- 在左边,那么有 ,那么有
- 右边同理,有
最后放代码:
#include<bits/extc++.h>#define int long longusing 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;}于是,颓废的一天就这么的过完了。
希望下次模拟赛能够不要切不出糖题。
