2021.5做题记录
P7530 [USACO21OPEN] United Cows of Farmer John P
考虑枚举 \(k\),同时对于每一个合法的 \(i\) 查询有多少个满足 \((i,j)\) 的数对的个数
那么我们需要维护这样几个操作
- 删除一个点
- 区间加
- 区间求和
线段树维护即可
P7532 [USACO21OPEN] Balanced Subsets P
显然这个东西长得类似于一个 sora 酱的攻击范围
\(f[i][l][r][0/1][0/1]\)表示第 \(i\) 行,选了 \(l\sim r\),左边是不是最宽的,右边是不是最宽的方案数
然后转移就是一个矩形前缀和的转移
\(\mathcal O(n^3)\)
一道计算几何的模板题
对于相同的 \(x\),显然只会有 \(y\) 最小的和最大的可能出现在凸包上,我们考虑求出这样的点
显然 \(x\) 坐标和 \(y\) 坐标到达一定层次之后就会出现循环节。
我们求出循环的区间,在 \(y\) 上每隔 \(x\) 的循环节长度个进行一下倍增、
然后最后求一遍凸包,就可以 \(\mathcal O(x\log n)\) 解决了
#include <bits/stdc++.h>
using namespace std;
# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)
typedef long long ll;
const int N=4e5+5;
template<typename T> void read(T &x){
x=0;int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
x*=f;
}
ll n;
int ax,ay,bx,by,px,py;
int x[N],y[N];
int lx,ly,cnt,top;
int st1[N][60],st2[N][60];
int to[N][60];
int pos[N];
int low[N],hig[N];
struct Vec{
int x,y;
Vec operator - (const Vec &a)const{
return (Vec){x-a.x,y-a.y};
}
ll operator * (const Vec &a)const{
return 1ll*x*a.y-1ll*y*a.x;
}
}p[N],s[N];
bool cmp1(Vec x,Vec y){
if(x.x!=y.x)return x.x<y.x;
return x.y<y.y;
}
bool cmp2(Vec x,Vec y){
ll z=(x-s[1])*(y-s[1]);
if(z)return z>0;
return x.x<y.x;
}
int main()
{
// freopen("testdata.in","r",stdin);
// freopen("test1.out","w",stdout);
memset(low,0x3f,sizeof(low));
memset(hig,-0x3f,sizeof(hig));
memset(pos,-1,sizeof(pos));
read(x[0]),read(y[0]),read(ax),read(ay),read(bx),read(by);
read(px),read(py),read(n);
pos[x[0]]=0;
int sx,sy;
while(1){
int val=(1ll*x[lx]*ax%px+bx)%px;
if(~pos[val]){
sx=pos[val];
break;
}
x[++lx]=val,pos[val]=lx;
}
lx++;
memset(pos,-1,sizeof(pos));
pos[y[0]]=0;
while(1){
int val=(1ll*y[ly]*ay%py+by)%py;
if(~pos[val]){
sy=pos[val];
break;
}
y[++ly]=val,pos[val]=ly;
}
ly++;
for(int i=sy;i<ly;i++)
st1[y[i]][0]=st2[y[i]][0]=y[i],to[y[i]][0]=y[(i+(lx-sx)-sy+ly-sy)%(ly-sy)+sy];
for(int k=1;k<60;k++)
for(int i=sy;i<ly;i++){
to[y[i]][k]=to[to[y[i]][k-1]][k-1];
st1[y[i]][k]=min(st1[y[i]][k-1],st1[to[y[i]][k-1]][k-1]);
st2[y[i]][k]=max(st2[y[i]][k-1],st2[to[y[i]][k-1]][k-1]);
}
for(int i=0;i<n&&i<max(sx,sy);i++){
int xx=i>=lx-1?x[(i-sx)%(lx-sx)+sx]:x[i];
int yy=i>=ly-1?y[(i-sy)%(ly-sy)+sy]:y[i];
low[xx]=min(low[xx],yy);
hig[xx]=max(hig[xx],yy);
}
for(int i=max(sx,sy);i<n&&i<max(sx,sy)+lx;i++){
int xx=i>=lx-1?x[(i-sx)%(lx-sx)+sx]:x[i];
int yy=i>=ly-1?y[(i-sy)%(ly-sy)+sy]:y[i];
low[xx]=min(low[xx],yy);
hig[xx]=max(hig[xx],yy);
ll siz=(n-(i+1))/(lx-sx)+1;
int now=yy;
_Rep(k,59,0)
if((1ll<<k)<=siz){
low[xx]=min(low[xx],st1[now][k]);
hig[xx]=max(hig[xx],st2[now][k]);
now=to[now][k];
siz-=1ll<<k;
}
}
for(int i=0;i<lx;i++)
if(low[x[i]]<1e9){
p[++cnt]=(Vec){x[i],low[x[i]]};
if(low[x[i]]!=hig[x[i]])p[++cnt]=(Vec){x[i],hig[x[i]]};
}
sort(p+1,p+cnt+1,cmp1);
s[++top]=p[1];
sort(p+2,p+cnt+1,cmp2);
Rep(i,2,cnt){
while(top>1&&(s[top]-s[top-1])*(p[i]-s[top])<=0)top--;
s[++top]=p[i];
}
ll ans=0;
s[top+1]=s[1];
Rep(i,1,top)ans+=(s[i]-s[1])*(s[i+1]-s[1]);
printf("%lld\n",ans);
return 0;
}
P7528 [USACO21OPEN] Portals G
考虑最开始连的边,\(2n\) 个点 \(2n\) 条边,每个点度数为 \(2\),显然是若干个环
考虑如果 \(p1,p3\) 不在一个环上,我们断开 \(p1,p2\) 和 \(p3,p4\),连接 \(p1,p3\) 和 \(p2,p4\),这样可以形成一个新的大环
所以我们把每个点对于权值排序,然后类似最小生成树即可
P7529 [USACO21OPEN] Permutation G
显然加入一个点,要么在原来的凸包里面,要么在外面,并且新的凸包还是一个三角形
用 \(f[i][j][k]\) 表示以 \(i,j,k\) 为顶点的方案数
但是发现三角形内部的点我们可以随便放在哪里,这样会算错
所以用 \(f[i][j][k][cnt]\) 表示已经选了 \(cnt\) 个,额外转移选取内部的即可
考虑如何判断一个点在一个三角形内部
相当于 \(\operatorname{sgn}(\vec{AB}\times \vec{AP})\neq \operatorname{sgn}(\vec{AC}\times \vec{AP})\) ,以及 \(B\) 出发和 \(C\) 出发
总的转移复杂度是 \(\mathcal O(n^5)\) 的
P6773 [NOI2020] 命运
考虑朴素的 dp,我们用 \(f[u][i]\) 表示 \(u\) 到根节点路径上最近的选了的边的距离为 \(i\)
转移非常的显然:
但是这样不太好优化,因此更改状态, \(f[u][i]\) 表示 \(u\) 到根节点路径上,最近的选的边的深度为 \(i\),这样转移的时候 \(i\) 就不动了
我们脑洞大发一下,发现我们要支持的是几个操作
- 单点查询
- 区间加上 \(f[v][0]\)
- 将对应位置的数分别乘起来
我们把 \(f[u]\) 建成一棵线段树,直接线段树合并即可
对于第三个操作,我们在线段树合并到最叶子节点位置的时候直接打乘法标记即可
P5666 [CSP-S2019] 树的重心
思路非常巧妙的好题
首先发现对于给定的局面,有可能出现一个重心或者两个重心,这个东西不太好搞,所以转化为求每个点作为重心的次数
设割掉的和 \(u\) 不在一个连通块的连通块大小为 \(S\)
那么割边可能出现三种不同的情况,设 \(f[u]\) 为 \(u\) 的重儿子的大小,\(g[u]\) 为次重儿子的大小
1.在 \(u\) 子树外
对于这种情况,我们需要满足
和
2.在 \(u\) 子树内,在 \(u\) 的重儿子内
3.在 \(u\) 子树内,不在 \(u\) 重儿子内
首先考虑第一种情况如何计算
考虑所有的在 \(rt\to i\) 路径上的点,他们的权值都是 \(n-siz[x]\),否则都是 \(siz[x]\)
所以最开始先把所有的 \(siz[x]\) 放到一个树状数组里面,然后 dfs 的时候每次更新一下当前节点的权值即可
但是这样我们会算进去子树里面的所有的 \(siz[x]\) 对于答案的贡献
所以我们再开一个树状数组,计算子树里的贡献,顺便维护这个的时候就可以求出 2,3 类对答案的贡献了
\(\mathcal O(n\log n)\)
U155893 Treasure
考虑一个充分条件
- 每种类型有的钥匙 $\geq $ 没有的钥匙
- 考虑钥匙可以重复用很多次,一定可以把所有的箱子都打开
实际上猜一下结论发现这个东西是必要的
证明没看懂
但是这样就可以每一轮从小到大枚举, \(\mathcal O(n)\) 判断条件2
这样的复杂度就是 \(\mathcal O(n^3)\) 的了
U160942 Don't Break The Nile
实际上是求一个最小割
考虑在建筑之间连边,然后从左到右跑最短路即可
考虑连边的权值,分 x 轴 y 轴都没有交集和有一维有交集分类讨论即可
P4332 [SHOI2014]三叉神经树
考虑一个点的修改,假设从 0 变成 1
那么他的父亲节点当且仅当只有 2 个 1 的时候会变化
同理,他的父亲的父亲的节点当且仅当只有 2 个 1 的时候会变化
所以我们可以直接维护区间 1 的子节点的个数的最小值和 0 的个数的最大值,每次二分,用树剖做到 \(\mathcal O(n\log ^3 n)\),用 lct 做到 \(\mathcal O(n\log ^2n)\)
但是这样依然太慢了
我们可以直接 lct 维护一条链上的最深的每一类的点
这样就可以 \(\mathcal O(n\log n)\) 了
SP2939 QTREE5 - Query on a tree V
考虑 \(dis(u,v)\) 可以被表示为 \(dep[u]+dep[v]-2dep[lca]\)
发现对于每个 \(v\),\(lca\) 一定都在这个点到根的路径上
对于这个路径上的每一个点,我们需要维护的是他的虚子树里的每一个点 \(x\),\(dep[x]-2dep[lca]\) 的最小值
这就是一个 lct 维护虚子树的问题,我们对于每一个点开一个可删堆,随便维护一下就好了
P5336 [THUSC2016]成绩单
做法显然看数据范围和题面就是区间 dp
可以发现一个非常显然的事情,假如原来位置上有 \(a,b,c,d\) 四个位置上的数,并且 \(a,b\) 是一起取得,\(c,d\) 是一起取得,那么 \(c,d\) 一定不会夹在 \(a,b\) 中间
也就是说,对于一段区间 \([l,r]\),他被分成了若干段,那么可以和其他段合并的部分一定在这个区间的两端的那两段是可以的
同时注意到我们只关心最终的答案,所以我们可以默认只在最左边的部分
用 \(f[i][j][x][y]\) 表示 \([i,j]\),可以合并的那一段的值域是 \([x,y]\) 的方案数(注意到 \(n\) 只有 \(50\) 所以只需要离散化)
转移分两种情况
- 直接在最右边接上一段单独的,\(f[i][k][x][y]=\min\{f[i][j][x][y]+f[j+1][k][x^\prime][j^\prime]\}\)
- 在右边接上一段可以和左边合并到一起的,\(f[i][k][\min(x,\min(a[j+1\cdots k]))][\max(y,\max(a[j+1\cdots k]))]=\min\{f[i][j][x][y]-calc(x,y)+calc(x_{new},y_{new})\}\)
直接转移即可 \(\mathcal O(n^5)\)
P5794 [THUSC2015]解密运算
首先可以手玩一下样例,发现可以得到一个 \(\mathcal O(n^2)\) 的做法
其实这个时候做法差不多就出来了
发现每次转移都是从一个转移过来的
所以直接预处理一个转移,然后 \(\mathcal O(n)\) 直接跑一遍就好了
P5795 [THUSC2015]异或运算
发现 \(n,q\) 都很小,所以考虑暴力枚举这两个东西
然后对于 \(m\) 开一个可持久化 trie,从高往低试着填就好了
CF1516D Cut
显然一个区间合法当且仅当其中两两互质
首先可以预处理出每个点右边最近的一个和他不互质的位置 \(r[i]\)
然后我们对于每一个 \(i\) 要找到最大的位置 \(k,s.t. \forall j\in [i,k-1],r[j] < k\)
这个直接在线段树上二分即可
然后倍增
P4681 [THUSC2015]平方运算
显然做法与 \(P\) 有关,并且能猜到跟循环节有关
打表发现这些模数的循环节的 lcm 都不超过 60,并且每一个数最多 11 次进到循环节里
所以可以预处理循环节,没进到循环节里的时候暴力修改
进去之后我们每个点维护 60 个数,分别表示现在开始平移 x 次可以变成的和
这样的话就可以区间修改了
\(\mathcal O(60n\log n)\)
P5301 [GXOI/GZOI2019]宝牌一大堆
显然国士无双和七对可以直接特判掉
剩下的情况考虑 dp
\(f[i][j][k][0/1][x][y]\) 表示第 \(i\) 种牌,用了 \(j\) 张牌,有 \(k\) 个杠,有没有雀头,\(i+1\) 选了 \(x\) 张,\(i+2\) 选了 \(y\) 张,最大价值
直接瞎转移就可以了
但是这样会 T
观察一下发现杠是不需要考虑的,因为 \(\binom{4}{3}>\binom{4}{4}\times 2\)
所以可以少掉一维,直接转移一下就好了
但是实际上可以直接记录有几个刻子有几个对就好了
P5298 [PKUWC2018]Minimax
显然可以得到一个 \(\mathcal O(n^2)\) 的 dp,设 \(f[i][j]\) 表示点 \(i\) 是 \(j\) 的概率
注意到只有两棵子树,所以可以写出转移
然后发现这个式子是前缀和的形式,考虑常见的树形 dp 优化,长剖不太行,因此考虑线段树合并
我们在合并的过程中可以直接算出前缀和和后缀和,因此可以直接线段树合并
注意线段树合并维护树形 dp 其实只需要考虑有一个子节点空了的时候的答案怎么维护即可,其他的直接由 pushup 完成