5.26 考试修改+总结
论写5K+的代码在只有样例的条件下都可以调对
由此可见,勇气才是成功的关键
先放题解吧
第一题上午写的暴力不小心忘记题目换根之后还会染色了
然后就挂成了5分QAQ
有很大的部分分是SDOI染色,还有一部分是旅行
但是考试犯懒没有写
很容易发现任何一种颜色在树上都是连续的一段
那么我们不妨这么定义,如果一条边两端颜色不相同,我们定义为虚边,会对子树每个答案产生+1的贡献
如果两端颜色相同,我们定义为实边,不会产生贡献
不难发现,这样定义后的实边和虚边的性质和LCT的定义是一样的
我们考虑使用LCT来维护,每次修改到根的路径就是一个Access
每次Access会使一些实边变成虚边,一些虚边变成实边
很容易在Access的过程中确定这些边,可以发现这些边的数量是等价于LCT复杂度的
也就是单次均摊O(logn),我们对于每次边的变化在对DFS序维护一颗线段树进行子树加减和子树求和就可以了
注意到这里LCT的儿子并不是原树中的儿子,所以我们要对于每个点维护他一直向左走到达的点L
这样修改的目标节点是LCT中的儿子的L,但是由于有换根操作,我们会有rev标记,所以我们还要额外维护一直向右走的点R
我们考虑换根操作,LCT显然可以直接换根
对于我们的线段树,我们可以使用类似于BZOJ 遥远的国度 的讨论方法,可以在不换根的情况下讨论出换根的信息
具体讨论方法就不在多说了,注意如果是菊花树,当根在当前点的子树内的时候,暴力找会挂掉
所以我们对于每个点开vector记录孩子,然后把孩子按DFS序排序,每次二分即可
总时间复杂度O(nlog^2n)
然后ftp挂掉了,没有数据的情况下我居然调了调就A了!撒花~~
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> #include<vector> using namespace std; typedef long long LL; const int maxn=100010; int n,m,u,v,rt; LL ans,sz; char s[maxn]; int h[maxn],cnt=0; struct edge{ int to,next; }G[maxn<<1]; int pos[maxn],ed[maxn],tot=0; vector<int>V[maxn]; LL S[maxn<<2]; int Add[maxn<<2]; int siz[maxn]; bool cmp(const int &x,const int &y){ return pos[x]<pos[y]; } void add(int x,int y){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt; } void push_down(int o,int L,int mid,int R){ int cl=(o<<1),cr=(o<<1|1); int now=Add[o];Add[o]=0; S[cl]+=now*(mid-L+1);Add[cl]+=now; S[cr]+=now*(R-mid);Add[cr]+=now; } void UPD(int o,int L,int R,int x,int y,int v){ if(L>=x&&R<=y){ S[o]=S[o]+v*(R-L+1); Add[o]+=v; return; } int mid=(L+R)>>1; if(Add[o]!=0)push_down(o,L,mid,R); if(y<=mid)UPD(o<<1,L,mid,x,y,v); else if(x>mid)UPD(o<<1|1,mid+1,R,x,y,v); else {UPD(o<<1,L,mid,x,y,v);UPD(o<<1|1,mid+1,R,x,y,v);} S[o]=S[o<<1]+S[o<<1|1]; } LL ask(int o,int L,int R,int x,int y){ if(L>=x&&R<=y)return S[o]; int mid=(L+R)>>1; if(Add[o]!=0)push_down(o,L,mid,R); if(y<=mid)return ask(o<<1,L,mid,x,y); else if(x>mid)return ask(o<<1|1,mid+1,R,x,y); else return ask(o<<1,L,mid,x,y)+ask(o<<1|1,mid+1,R,x,y); } int find_pos(int u,int v){ int L=0,R=V[u].size()-1; while(L<R){ int mid=L+((R-L+1)>>1); int now=V[u][mid]; if(pos[v]>=pos[now])L=mid; else R=mid-1; }return V[u][L]; } void Get_modify(int u,int val){ if(u==rt)UPD(1,1,n,1,n,val); else if(pos[rt]<=ed[u]&&pos[rt]>=pos[u]){ int v=find_pos(u,rt); UPD(1,1,n,1,n,val); UPD(1,1,n,pos[v],ed[v],-val); }else UPD(1,1,n,pos[u],ed[u],val); } LL Get_ans(int u){ if(u==rt)return ask(1,1,n,1,n); else if(pos[rt]<=ed[u]&&pos[rt]>=pos[u]){ int v=find_pos(u,rt); LL A=ask(1,1,n,1,n); LL B=ask(1,1,n,pos[v],ed[v]); return A-B; }else return ask(1,1,n,pos[u],ed[u]); } int Get_sz(int u){ if(u==rt)return n; else if(pos[rt]<=ed[u]&&pos[rt]>=pos[u]){ int v=find_pos(u,rt); return n-siz[v]; }else return siz[u]; } struct link_cut_tree{ int fa[maxn],c[maxn][2]; int rev[maxn],L[maxn],R[maxn]; #define fa(u) fa[u] #define c(u,i) c[u][i] #define rev(i) rev[i] #define L(i) L[i] bool isroot(int u){return c(fa(u),0)!=u&&c(fa(u),1)!=u;} void pre(int p){if(!isroot(p))pre(fa(p));down(p);} void flip(int u){swap(c(u,0),c(u,1));swap(L[u],R[u]);rev(u)^=1;} void init(){for(int i=1;i<=n;++i)L[i]=i,R[i]=i;} void down(int u){ if(rev(u)){ if(c(u,0))flip(c(u,0)); if(c(u,1))flip(c(u,1)); rev(u)=0; }return; } void up(int u){ if(c(u,0))L[u]=L[c(u,0)]; else L[u]=u; if(c(u,1))R[u]=R[c(u,1)]; else R[u]=u; } void rotate(int p,int x){ int mark= p==c(x,1),y=c(p,mark^1),z=fa(x); if(c(z,0)==x)c(z,0)=p; if(c(z,1)==x)c(z,1)=p; if(y)fa(y)=x; fa(p)=z;c(p,mark^1)=x;fa(x)=p;c(x,mark)=y; up(x); } void Splay(int p){ pre(p); while(!isroot(p)){ int x=fa(p),y=fa(x); if(isroot(x))rotate(p,x); else if(p==c(x,0)^x==c(y,0))rotate(p,x),rotate(p,y); else rotate(x,y),rotate(p,x); }up(p);return; } void Access(int u){ for(int v=0;u;u=fa(u)){ Splay(u); fa(v)=u; if(c(u,1))Get_modify(L[c(u,1)],1); c(u,1)=v; if(v)Get_modify(L[v],-1); v=u; }return; } void make_root(int u){Access(u);Splay(u);flip(u);} }LCT; void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } void Get_DFS(int u,int f){ pos[u]=++tot;siz[u]=1; LCT.fa[u]=f; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(v==f)continue; V[u].push_back(v); Get_DFS(v,u); siz[u]+=siz[v]; }ed[u]=tot; } int main(){ read(n);read(m); for(int i=1;i<n;++i){ read(u);read(v); add(u,v);add(v,u); }Get_DFS(1,0);rt=1; LCT.init(); for(int i=1;i<=n;++i)sort(V[i].begin(),V[i].end(),cmp); for(int i=1;i<=n;++i)UPD(1,1,n,pos[i],ed[i],1); for(int i=1;i<=m;++i){ scanf("%s",s+1); read(u); if(s[3]=='Q'){ ans=Get_ans(u); sz=Get_sz(u); printf("%.10lf\n",(double)(ans)/(double)(sz)); }else if(s[3]=='L'){ LCT.Access(u); }else{ //rt=u; LCT.make_root(u); rt=u; } }return 0; }
第二题很容易发现用组合数算出取哪k个,然后这k个数组成一个置换的方案是(k-1)!,剩余的数就是一个错排函数f
那么就是C(n,k)*(k-1)!*f(n-k)
但是我们发现对于同一个序列,因为它可能有多个k个数组成一个置换的情况,所以我们会算重
然后容斥一下就好了,容斥的式子也是很容易推导的
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; const int maxn=500010; const int mod=1e9+7; int n; LL jc[maxn],inv[maxn]; LL f[maxn],g[maxn]; LL ans=0; LL pow_mod(LL v,int p){ LL tmp=1; while(p){ if(p&1)tmp=tmp*v%mod; v=v*v%mod;p>>=1; }return tmp; } LL C(int n,int m){ return jc[n]*inv[m]%mod*inv[n-m]%mod; } int main(){ scanf("%d",&n); jc[0]=1; for(int i=1;i<=n;++i)jc[i]=jc[i-1]*i%mod; inv[n]=pow_mod(jc[n],mod-2); for(int i=n-1;i>=0;--i)inv[i]=inv[i+1]*(i+1)%mod; f[0]=1;f[1]=0; for(int i=2;i<=n;++i)f[i]=(f[i-1]+f[i-2])*(i-1)%mod; g[0]=1;g[1]=0;g[2]=1; for(int i=3;i<=n;++i)g[i]=g[i-1]*(i-1)%mod; for(int k=2;k<=n;++k){ int cnt=0; LL tmp=1; for(int j=k;j<=n;j+=k){ cnt++; tmp=tmp*C(n-j+k,k)%mod; tmp=tmp*g[k]%mod; if(cnt&1)ans=ans+tmp*inv[cnt]%mod*f[n-j]%mod; else ans=ans-tmp*inv[cnt]%mod*f[n-j]%mod; if(ans<0)ans+=mod; if(ans>=mod)ans-=mod; } }printf("%lld\n",(ans%mod+mod)%mod); return 0; }
第三题是个非常丝薄的题目
首先我们搞出这个序列,不难发现题目要求求极长最长上升子序列的个数
由于n<=1000,直接n^2暴力DP就可以了
考试的时候一直在想这个题目是可以O(n)做的啊,后来才发现读入复杂度都是O(n^2)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> using namespace std; const int maxn=1010; const int oo=0x7fffffff; const int mod=1e9+7; int n,m,u,v; int deg[maxn]; int a[maxn]; int Num[maxn]; int dp[maxn]; bool vis[maxn][maxn]; void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } int main(){ read(n);read(m); for(int i=1;i<=m;++i){ read(u);read(v); if(u>v)swap(u,v); deg[u]++; } for(int i=1;i<=n;++i)Num[i]=i; for(int i=1;i<=n;++i){ int now=deg[i]+1; a[i]=Num[now]; for(int j=now;j<=n;++j)Num[j]=Num[j+1]; } n++;a[0]=0;a[n]=n; for(int i=0;i<=n;++i){ int mn=oo; for(int j=i+1;j<=n;++j){ if(a[j]>a[i]){ if(mn>a[j])vis[i][j]=true; mn=min(mn,a[j]); } } } dp[0]=1; for(int i=1;i<=n;++i){ for(int j=0;j<i;++j){ if(vis[j][i]){ dp[i]+=dp[j]; if(dp[i]>=mod)dp[i]-=mod; } } }printf("%d\n",(dp[n]%mod+mod)%mod); return 0; }
今天考试比较好的地方:
1、很快的发现第二题的容斥并成功的推出了式子
2、第三题的模型很容易就看了出来
然后就A掉了第二题和第三题
不太好的地方:
1、第一题的花式暴力分懒得去写(导致最后连最简单的暴力都挂掉了,关键是没有用心)
2、第一题想到了维护类似于实边和虚边的东西,但是发现单次可能挂成O(n)就没有往下想
实际上在分析会发现均摊是O(logn)的
需要做的题目:SDOI 旅行
话说天天都坑着一堆题目要做。。