The 2019 ICPC Asia Shanghai Regional Contest 补题
重现赛过了三道题,我做出来了F题,其他题面都看了看没想到思路~
挨个看题意看到了F,题意不就n个点的树,每个点有初始点权,需要维护m次操作:操作1:u到v的路径上的点权变成w。2:u到v的路径上的点权加w。3:u到v的路径上的点权乘w。4:求u到v的路径上的点权立方和。树上路径我会lca和树剖,要做到这样程度的修改用树剖+线段树比较合适。那么问题变成了维护区间立方和。我之前见过维护区间平方和的题,非常厉害,只需要维护区间和与区间平方和,每次pushdown的时候手推一下式子即可。我把三次方的式子(x+a)^3展开之后发现也很可写。那就开始码呗,想当年我可是连主席树都会写的人。码了半年才码出来,边的结构体还开小了,越界了好多发。
A数多米诺骨牌的数量。RiCi太大了但是我会离散化。一个n^2的做法是预处理出每个点向右向下延伸的最长长度,然后先枚举点再枚举骨牌的大小,O(1)的判断能否组成。写着写着发现O(1)查询有点难搞,最后用了map多了个log(捂脸),不出意外的在第一组数据T飞了。
#include<bits/stdc++.h> using namespace std; const int N=5000010; int n; struct node { int x,y; friend bool operator < (node a,node b) { return a.x==b.x?a.y<b.y:a.x<b.x; } }o[N]; map<node,int>maxx,maxy; bool cmpx(node a,node b) { return a.x==b.x?a.y<b.y:a.x<b.x; } bool cmpy(node a,node b) { return a.y==b.y?a.x<b.x:a.y<b.y; } int main() { int T,k,n;scanf("%d",&T); for(k=1;k<=T;k++) { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&o[i].x,&o[i].y); maxx.clear(); maxy.clear(); sort(o+1,o+1+n,cmpx); for(int i=n;i;i--) if(o[i].x==o[i+1].x&&o[i].y==o[i+1].y-1) maxy[node{o[i].x,o[i].y}]=maxy[node{o[i+1].x,o[i+1].y}]+1; else maxy[node{o[i].x,o[i].y}]=1; sort(o+1,o+1+n,cmpy); for(int i=n;i;i--) if(o[i].y==o[i+1].y&&o[i].x==o[i+1].x-1) maxx[node{o[i].x,o[i].y}]=maxx[node{o[i+1].x,o[i+1].y}]+1; else maxx[node{o[i].x,o[i].y}]=1; int ans=0; for(int i=1;i<=n;i++)//1*2 { int a=maxx[node{o[i].x,o[i].y}],b=maxy[node{o[i].x,o[i].y}]; for(int xx=1,yy=2;xx<=a&&yy<=b;xx++,yy+=2) if(maxy[node{o[i].x+xx-1,o[i].y}]>=yy&&maxx[node{o[i].x,o[i].y+yy-1}]>=xx) ans++; for(int xx=2,yy=1;xx<=a&&yy<=b;xx+=2,yy++) if(maxy[node{o[i].x+xx-1,o[i].y}]>=yy&&maxx[node{o[i].x,o[i].y+yy-1}]>=xx) ans++; } printf("Case #%d: %d\n",k,ans); } }
写完了O(1)查询的代码,竟然A掉了?
#include<bits/stdc++.h> using namespace std; const int N=5000010; int n; struct node { int x,y,i }o[N]; int maxx[N],maxy[N],xi[N],ix[N],yi[N],iy[N]; bool cmpx(node a,node b) { return a.x==b.x?a.y<b.y:a.x<b.x; } bool cmpy(node a,node b) { return a.y==b.y?a.x<b.x:a.y<b.y; } bool cmpi(node a,node b) { return a.i<b.i; } int main() { int T,k,n;scanf("%d",&T); for(k=1;k<=T;k++) { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&o[i].x,&o[i].y),o[i].i=i; o[n+1].x=0; o[n+1].y=0; o[n+1].i=0; sort(o+1,o+1+n,cmpx); for(int i=n;i;i--) { xi[i]=o[i].i; ix[o[i].i]=i; if(o[i].x==o[i+1].x&&o[i].y==o[i+1].y-1) maxy[o[i].i]=maxy[o[i+1].i]+1; else maxy[o[i].i]=1; } sort(o+1,o+1+n,cmpy); for(int i=n;i;i--) { yi[i]=o[i].i; iy[o[i].i]=i; if(o[i].y==o[i+1].y&&o[i].x==o[i+1].x-1) maxx[o[i].i]=maxx[o[i+1].i]+1; else maxx[o[i].i]=1; } sort(o+1,o+1+n,cmpi); int ans=0; for(int i=1;i<=n;i++)//1*2 { int a=maxx[i],b=maxy[i]; for(int xx=1,yy=2;xx<=a&&yy<=b;xx++,yy+=2) if(maxy[yi[iy[i]+xx-1]]>=yy&&maxx[xi[ix[i]+yy-1]]>=xx) ans++; for(int xx=2,yy=1;xx<=a&&yy<=b;xx+=2,yy++) if(maxy[yi[iy[i]+xx-1]]>=yy&&maxx[xi[ix[i]+yy-1]]>=xx) ans++; } printf("Case #%d: %d\n",k,ans); } }
B题队友A掉了,看了题意,好想好写。问的是一堆字符串有没有是别人的前缀的。考虑读进来,sort之后用map维护即可。
#include<bits/stdc++.h> using namespace std; map<string,int>mp; string s[10010],t; int len,n,T,i; int main() { cin>>T; for(int k=1;k<=T;k++) { cin>>n; mp.clear(); for(i=1;i<=n;i++) cin>>s[i]; sort(s+1,s+1+n); for(i=1;i<=n;i++) { len=s[i].length(); t.clear(); for(int j=0;j<len;j++) { t+=s[i][j]; if(mp[t]) { j=len+2; i=n+1; break; } } mp[s[i]]=1; } printf("Case #%d: ",k); if(i==n+2) printf("No\n"); else printf("Yes\n"); } }
C就是在一个N*M矩形内左下角和右上角不能走,同时给定S个有财宝的点,K个有障碍的点不能走。问从左上角到右下角的方案数。我一看S才10,想到了容斥+二维DP。但是看到N、M<=100000就自闭了,DP不太行,容斥依然还行。注意到每一列的dp方程可以写矩阵乘法,但那是n^2的,不行。 想到了容斥之后问题就变成了M*N的矩阵左下角大小为a右上角大小为b的角落不能走的方案数,我和yxh想了很久也没想到,溜了溜了。
E题是在一个有点权的矩形中规定起点和终点,从一个点走到另一个点时如果下一个点没走过就可以使答案+两个点权的乘积。