前言
其实我也想直接上题解,但这会让不想颓的人无意间颓到个标签之类的尴尬事件发生。
所以,我的每篇题解还是会说些废话的……
这次不想说太多,只想提一句:
所有题全部#define int long long!!!
内存允许的话能开long long就开long long!!!
一定考虑要不要开long long!!!
T1
正解是差分。
然而我打了一颗线段树(事实上我只是拿线段树将差分$\Theta(1)$标记的过程生硬的改成$\Theta(logN)$的区间加……)。
我们建n棵线段树。
假设要修改(r,c),长度为l,增加的权值是s。
我们先将第r行所对应的线段树的[r,c]都加上s,然后在c-1的位置累加上-s的标记1,c的位置累加上s的标记2。
标记1和2是不同的标记,标记1下传到下一行的同一位置,标记2下传到下一行的下一位置,即min(c+1,n)。
每次下传标记时进行一次1到目标位置的区间加法,例如第x行第y个位置下传标记1时需要进行一次第x+1行[1,y]的区间加。
标记1最初赋值为-s,所以是区间加不是区间减。
因为长度只有l,所以需要对第r+l-1行c-1的位置的标记1累加s,第r+l-1行min(c+l-1,n)的位置的标记2累加-s进行抵消。
最后遍历每一行所对应的线段树,将区间加的懒标记全部下传以确保单点权值的正确。
遍历到单点时权值与ans进行异或操作即可。
注意打标记是$\Theta(1)$的,因为相同纵坐标的位置在每棵线段树的位置(即数组下标)相同。
总时空复杂度$\Theta(N^2logN)$。
然而我按这个思路交上去的代码爆零了……
这……q=0我都没过,why?
!!!
……
然而还是WA。
我百思不得其解,在调了半个小时后听从Joe神的劝告#define int long long,结果:
???
在我认真调试后发现我的线段树add函数传参中增量的值开成了int,然而它要开longlong(因为下传标记时进行区间加的增量是longlong)。
#include<cstdio> #define ll long long #define L k<<1 #define R k<<1|1 using namespace std; int const N=1003; inline int read(){ int ss=0;char bb=getchar(); while(bb<48||bb>57)bb=getchar(); while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar(); return ss; } int n,q,id[N]; ll ans; inline int min(int x,int y){ return x<y?x:y; } struct S_tree{ struct node{ ll w,f,mf,cf; }a[N<<2]; inline void down(int k,int x,int y){ if(!a[k].f || x==y)return ; ll z=a[k].f; int mid=x+y>>1; a[k].f=0; a[L].w+=z*(mid-x+1),a[L].f+=z; a[R].w+=z*(y-mid),a[R].f+=z; return ; } inline void update(int k){ a[k].w=a[L].w+a[R].w; return ; } void add(int l,int r,int x,int y,ll p,int k){ if(l>=x && r<=y){a[k].w+=p*(r-l+1),a[k].f+=p;return ;} down(k,l,r); int mid=l+r>>1; if(x<=mid)add(l,mid,x,y,p,L); if(y>mid)add(mid+1,r,x,y,p,R); return update(k); } void qj(int l,int r,int k){ if(l==r){ans^=a[k].w;return ;} down(k,l,r); int mid=l+r>>1; qj(l,mid,L),qj(mid+1,r,R); return ; } }tr[N]; void dfs(int now,int x,int y,int k){ if(x==y){ ll z; if(tr[now].a[k].mf){ z=tr[now].a[k].mf; tr[now+1].add(1,n,1,min(x+1,n),z,1); tr[now+1].a[id[min(x+1,n)]].mf+=z; } if(tr[now].a[k].cf){ z=tr[now].a[k].cf; tr[now+1].add(1,n,1,x,z,1); tr[now+1].a[id[x]].cf+=z; } ans^=tr[now].a[k].w; return ; } tr[now].down(k,x,y); int mid=x+y>>1; dfs(now,x,mid,L),dfs(now,mid+1,y,R); return ; } void get(int x,int y,int k){ if(x==y){id[x]=k;return ;} int mid=x+y>>1; get(x,mid,L),get(mid+1,y,R); return ; } signed main(){ //freopen("1.in","r",stdin); //freopen("1.out","w",stdout); n=read(),q=read(); if(!q)return puts("0"),0; get(1,n,1); while(q--){ int r=read(),c=read(),l=read(); ll s=read(); tr[r].add(1,n,c,c,s,1); tr[r].a[id[c]].mf+=s; if(c^1){ tr[r].a[id[c-1]].cf-=s; if(r+l-1<=n)tr[r+l-1].a[id[c-1]].cf+=s; } if(r+l-1<=n)tr[r+l-1].a[id[min(c+l-1,n)]].mf-=s; } for(register int i=1;i<n;++i) dfs(i,1,n,1); tr[n].qj(1,n,1); printf("%lld",ans); return 0; }
T2
相当于记忆化搜索。
记录状态(st,x)下的搜索答案,再次搜到直接回溯。
其中st是剩余球的状态,x是进行到第几次操作。
注意位运算的运用可大大缩短运行时间。
还有搜索第x次操作时对于长度为n-x+1的可行域我们只搜一半即可,另一半是一样的。
对(st,x)的状态记录需要用Hash表。
一般都记录了点对,但因为x值域只有[1,30],所以我直接开了30个Hash表,插入和查询状态(st,x)时直接在第x个Hash表中操作。
复杂度并不会证明,据说状态上界为$\sum\limits_{i=1}^{n+1}Fib(i)$。
我的代码double改成float是可以AC的,但有的代码AC不了。可能我是欧洲人?
#include<cstdio> using namespace std; int const N=31,MOD=7e5+1,M=7e5+1; int n,k,ct,cc,lt; struct node{ int head[MOD],Next[M],to[M],t; double ww[M]; inline int find(int x){ for(register int i=head[x%MOD];i;i=Next[i]) if(to[i]==x)return i; return 0; } inline void ins(int x,double z){ int now=x%MOD; to[++t]=x,ww[t]=z; Next[t]=head[now],head[now]=t; return ; } }Hash[N]; inline double max(double x,double y){ return x>y?x:y; } inline int del(int x,int y){ return ((x&(lt-1<<y))>>1)|(x&((1<<y-1)-1)); } double dfs(int x,int ak,int rm){ if(x>k)return rm; int r=n-x+1,lit=r>>1,lp=1,rp=n,cp,now=ak&((1<<r)-1); if((cp=Hash[x].find(ak)))return Hash[x].ww[cp]; double ans(0); for(int i=1;i<=lit;++i) ans+=2*max(dfs(x+1,del(ak,i),rm+((now>>i-1)&1)), dfs(x+1,del(ak,r-i+1),rm+((now>>r-i)&1))); if(r&1)ans+=dfs(x+1,del(ak,lit+1),rm+((now>>lit)&1)); ans/=(double)r; Hash[x].ins(ak,ans); return ans; } int main(){ scanf("%d%d",&n,&k),lt=1<<n; int st=0; char bb=getchar(); while(bb^'W'&&bb^'B')bb=getchar(); for(register int i=1;i<=n;++i,bb=getchar()) if(bb=='W')++ct,st|=1<<i-1; else ++cc; if(!k || cc==n)return puts("0.0000000000"),0; if(k==n){ printf("%d",ct); return puts(".0000000000"),0; } if(ct==n)return printf("%d.0000000000",k),0; printf("%.10lf",dfs(1,st,0)); return 0; }
T3
大神级题目,思路很不错。
题解请参考露迭月大神的博客。
注意dp[i][0]是不向上伸出翻转边,dp[i][1]是伸出。
时间复杂度$\Theta(N)$。
放一下我的代码:
#include<cstdio> #include<iostream> #define mk make_pair #define fr first #define sc second using namespace std; typedef pair<int,int> pi; int const N=1e5+5,lar=1e9+7; const pi lp=mk(lar,lar),rp=mk(0,0); inline int read(){ int ss=0;char bb=getchar(); while(bb<48||bb>57)bb=getchar(); while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar(); return ss; } int n,ans,tot; int head[N],Next[N<<1],to[N<<1],lt[N<<1],t; pi f[N][2]; inline void add(int x,int y,int z,int p){ to[++t]=y,lt[t]=p==2?2:(z^p); Next[t]=head[x],head[x]=t; return ; } pi operator + (pi x,pi y){ return mk(x.fr+y.fr,x.sc+y.sc); } void dfs(int x,int fa,int z){ pi a=lp,b=rp,c,d; for(int i=head[x],y;i;i=Next[i]) if((y=to[i])^fa){ dfs(y,x,lt[i]); c=min(a+f[y][0],b+f[y][1]),d=min(a+f[y][1],b+f[y][0]); a=c,b=d; } f[x][0]=z==1?lp:min(mk(a.fr+1,a.sc),b); f[x][1]=z?min(mk(a.fr,a.sc+1),mk(b.fr+1,b.sc+1)):lp; return ; } int main(){ n=read(); for(register int i=1,ff,tt,zz,pp;i<n;++i) ff=read(),tt=read(),zz=read(),pp=read(), add(ff,tt,zz,pp),add(tt,ff,zz,pp); dfs(1,0,0); printf("%d %d",f[1][0].fr>>1,f[1][0].sc); return 0; }