考虑题目的性质,可以发现蓝树与红树肯定有交,且必在最后一次割掉。
所以考虑反向过程,如何从红树变为蓝树。
将最后一次的情况往外推广,每一次也要有类似的交集,。
考虑如何去维护,通过启发式合并 $set$ 与并查集,时间复杂度 $O(n\log^2 n)$ 。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #include<set> #include<queue> using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1;c=getchar();} while(c>=‘0‘&&c<=‘9‘){ans=ans*10+c-‘0‘;c=getchar();} return f*ans; } const int MAXN=200001; map<pair<int,int> ,int> M; set<int> s[MAXN]; queue<pair<int,int> > que; void add(int u,int v){ s[u].insert(v),s[v].insert(u); if(u>v) swap(u,v); M[make_pair(u,v)]++; if(M[make_pair(u,v)]==2) que.push(make_pair(u,v)); return; } void del(int u,int v){ s[u].erase(v),s[v].erase(u); if(u>v) swap(u,v); M.erase(make_pair(u,v)); } set<int>::iterator it,it1; int n,f[MAXN]; int find(int x){if(f[x]==x) return x;return f[x]=find(f[x]);} bool solve(){ int tot=0; for(int i=1;i<=n;i++) f[i]=i; while(!que.empty()){ int u=que.front().first,v=que.front().second;que.pop(); tot++; u=find(u),v=find(v); if(s[u].size()<s[v].size()) swap(u,v); del(u,v); f[v]=u; for(it=s[v].begin();it!=s[v].end();){ it1=it;it++; int end=*it1;end=find(end); del(v,end),add(u,end); } } return tot==(n-1); } int main(){ freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); n=read(); for(int i=1;i<n;i++) add(read(),read()); for(int i=1;i<n;i++) add(read(),read()); if(solve()){printf("YES\n");return 0;} printf("NO\n");return 0; }
众数肯定是要枚举的,考虑暴力求出对于众数 $x$ 的个数。
设 $A_i$ 表示 $a_x=i$ 的出现次数,$B_i$ 亦如此。
则 $Ans_i=\sum min\{A_j,B_{i-j}\}$ 。这个式子很像能卷积的式子,但是无法计算 $min$ 操作。
但是可以将式子拆开去做,则 $$Ans_i=\sum_{x=1}^{n(k)} \sum_{j=0}^i [A_j\geq x]\times [B_{i-j}\geq x]\\=\sum_{x=1}^n\sum_{j=0}^i c_j\times d_{i-j}$$
时间复杂度 $O(k\times n\log n)$ 。
但是此刻 $k$ 与 $n$ 同阶。通过简单思考发现对于 $A$ 中出现 $k$ 次的不超过 $\dfrac{n}{k}$ 个,所以可以通过设置 $k$,$NTT$ 与暴力同时处理两种不同情况。
时间复杂度 $O(k\times n\log n+\dfrac{n^2}{k^2})$ ,在 $k$ 取 $10$ 时应为最优。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define int long long #define mod 998244353 using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1;c=getchar();} while(c>=‘0‘&&c<=‘9‘){ans=ans*10+c-‘0‘;c=getchar();} return f*ans; } const int MAXN=600001; namespace Poly{ int f[MAXN],g[MAXN],flip[MAXN]; inline int ksm(int a,int b){ int ans=1; while(b){ if(b&1) ans*=a,ans%=mod; a*=a,a%=mod; b>>=1; }return ans; } inline int Get(int lim){ int len=1;while(len<=lim) len<<=1; for(register int i=0;i<len;i++) flip[i]=((flip[i>>1]>>1)|(i&1?len>>1:0)); return len; } inline void NTT(int *f,int lim,int opt){ for(register int i=0;i<lim;i++) if(i<flip[i]) swap(f[i],f[flip[i]]); for(register int p=2;p<=lim;p<<=1){ int len=p>>1,buf=ksm(3,(mod-1)/p); if(opt==-1) buf=ksm(buf,mod-2); for(register int be=0;be<lim;be+=p){ int tmp=1; for(register int l=be;l<be+len;l++){ int t=f[l+len]*tmp;t%=mod; f[l+len]=(f[l]-t+mod)%mod,f[l]=(f[l]+t)%mod; tmp*=buf,tmp%=mod; } } } if(opt==-1){ int Inv=ksm(lim,mod-2); for(register int i=0;i<lim;i++) f[i]*=Inv,f[i]%=mod; }return; } } int n,Maxn,a[MAXN],b[MAXN],A[MAXN],B[MAXN],f[MAXN],g[MAXN],ans[MAXN],staa[MAXN],stab[MAXN]; signed main(){ freopen("max.in","r",stdin); freopen("max.out","w",stdout); n=read(); for(int i=1;i<=n;i++) a[i]=read(),A[a[i]]++; for(int i=1;i<=n;i++) b[i]=read(),B[b[i]]++; int lim=Poly::Get(200000); for(register int i=1;i<=10;i++){ memset(f,0,sizeof(f)),memset(g,0,sizeof(g)); for(register int j=1;j<=100000;j++) f[j]=(A[j]>=i),g[j]=(B[j]>=i); Poly::NTT(f,lim,1),Poly::NTT(g,lim,1); for(register int j=0;j<lim;j++) f[j]*=g[j],f[j]%=mod; Poly::NTT(f,lim,-1); for(int j=0;j<lim;j++) ans[j]+=f[j]; } for(register int i=1;i<=100000;i++){ if(A[i]>10) staa[++staa[0]]=i; if(B[i]>10) stab[++stab[0]]=i; } for(register int i=1;i<=staa[0];i++) for(int j=1;j<=stab[0];j++) ans[staa[i]+stab[j]]+=min(A[staa[i]],B[stab[j]])-10; for(register int i=1;i<=200000;i++) Maxn=max(Maxn,ans[i]); printf("%lld\n",Maxn);return 0; }
考虑容斥原理,设 $g_i$ 表示至少有 $i$ 个点满足 $|a_j-j|=k$ 的方案数。
则 $Ans=\sum_i (-1)^i\times g_i\times (n-i)!$ 。
考虑建立二分图,左为下标,右为权值,若有边 $(u,v)$ ,则下标为 $u$ 不能选 $v$ 。
将二分图拉出来简单 $dp$ 即可。
时间复杂度 $O(n^2)$ 。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define int long long #define mod 924844033 using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1;c=getchar();} while(c>=‘0‘&&c<=‘9‘){ans=ans*10+c-‘0‘;c=getchar();} return f*ans; } const int MAXN=4001; int fac[MAXN],f[MAXN][MAXN][2],n,k,sta[MAXN],col[MAXN]; int Mod(int x){return ((x%mod)+mod)%mod;} signed main(){ freopen("count.in","r",stdin); freopen("count.out","w",stdout); n=read(),k=read();fac[0]=1; for(int i=1;i<=n;i++) fac[i]=(fac[i-1]*i)%mod; for(int i=1;i<=k;i++){ col[0]++; for(int j=i;j<=n;j+=k) sta[++sta[0]]=j,col[sta[0]]=col[0]; col[0]++; for(int j=i;j<=n;j+=k) sta[++sta[0]]=j,col[sta[0]]=col[0]; } f[1][0][0]=1; for(int i=2;i<=sta[0];i++){ for(int j=0;j<=n;j++){ f[i][j][0]=f[i-1][j][0]+f[i-1][j][1];f[i][j][0]=Mod(f[i][j][0]); if(j!=0&&col[i]==col[i-1]) f[i][j][1]=f[i-1][j-1][0],f[i][j][1]%=mod; } } int Ans=0,pw=1; for(int i=0;i<=n;i++){ Ans+=pw*fac[n-i]*((f[sta[0]][i][0]+f[sta[0]][i][1])%mod),Ans=Mod(Ans),pw*=-1; } printf("%lld\n",Ans);return 0; }
原文:https://www.cnblogs.com/si-rui-yang/p/11332482.html