kangaroo
题意:
有n(n<=300)只袋鼠,袋鼠i的体积为ai,口袋大小为bi(ai>bi)。
把袋鼠i放入袋鼠j的口袋里。
要求此时袋鼠i和j的口袋里没有袋鼠,且袋鼠i,j不在其它袋鼠口袋里。
不断操作直到不能操作为止,有多少种不同的终止状态,对109+7取模。
分析:
考虑一种合法的终止状态。
一定存在一只袋鼠,比它小的袋鼠都被装了,能装它的袋鼠都装了其它袋鼠。
先将a和b分别排序。
然后枚举这只倒霉的袋鼠i,dp算出方案数。
将a和b都按是否大于ai分成左右两边。
用f[i][j]表示a中左边前i个与b中左边匹配j对的方案数。
用g[i][j]表示b中右边前i个与a中右边匹配j对的方案数。
枚举a中左边与b中右边匹配的对数k,则这k对有k!种匹配方法。
再乘上ab两边分别匹配的方案数,就是所有的方案数。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define g() getchar() template<class Q>inline void Scan(Q&x){ char c; while(c=g(),c<48||c>57); for(x=0;c>47&&c<58;c=g())x=10*x+c-48; } #define rep(i,a,b) for(int i=a;i<b;++i) #define dat(i,a,b) for(int i=a;i>b;--i) const int MAXN=305; const int Mod=1e9+7; int n,ans; int fac[MAXN]; int f[MAXN][MAXN],g[MAXN][MAXN]; int a[MAXN],b[MAXN]; inline void Init(){ Scan(n); rep(i,1,n+1){ Scan(a[i]),Scan(b[i]); } sort(a+1,a+1+n); sort(b+1,b+1+n); fac[0]=1; rep(i,1,n+1) fac[i]=1ll*fac[i-1]*i%Mod; } inline void Solve(){ rep(wuke,1,n+1){ memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); int r=upper_bound(b+1,b+n+1,a[wuke])-b; f[0][0]=1; rep(i,1,r) rep(j,0,i+1){ f[i][j]=f[i-1][j]; int x=lower_bound(a+1,a+wuke,b[i])-a-1; if(j&&x-j+1>0){ f[i][j]=(f[i][j]+1ll*f[i-1][j-1]*(x-j+1)%Mod)%Mod; } } g[n+1][0]=1; dat(i,n,wuke) rep(j,0,n-i+2){ g[i][j]=g[i+1][j]; int x=n-(upper_bound(b+r,b+n+1,a[i])-b)+1; if(j&&x-j+1>0){ g[i][j]=(g[i][j]+1ll*g[i+1][j-1]*(x-j+1)%Mod)%Mod; } } int s=min(wuke-1,n-r+1); rep(i,0,s+1){ ans=(ans+1ll*f[r-1][wuke-1-i]*g[wuke+1][n-r+1-i]%Mod*fac[i]%Mod)%Mod; } } printf("%d\n",ans); } int main(){ freopen("kangaroo.in","r",stdin); freopen("kangaroo.out","w",stdout); Init(); Solve(); return 0; }
loneliness
题意:
给出一棵以1为根的有向树,每个节点上有一个字符(只会是小写字母)。
多次询问,给出一个字符集合,要求统计树上合法的串的数量。
合法的串满足4个要求:
1.是树上某个点向根方向的走所形成的串
2.串中所有的字符来自给定的字符集合
3.给定的字符集合中的每一个字符都在串中出现过
4.没有被任意一个满足前三条条件的路径完全包含
树的节点数和询问次数均不超过105
分析:
如果一个路径合法的串,当且仅当该路径向两端延伸1个位置都不能有在路径出现的字符。
我们记录每个节点向根方向的最近的出现某个字符的节点,显然合法的串只可能出现在这条路径上。
再记录每个节点所有儿子的字符集合,对于每个节点按深度枚举要走到的字符,判断是否合法。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define g() getchar() template<class Q>inline void Scan(Q&x){ char c; while(c=g(),c<48||c>57); for(x=0;c>47&&c<58;c=g())x=10*x+c-48; } #define rep(i,a,b) for(int i=a;i<b;++i) const int MAXN=50005; const int Alpha=26; typedef long long ll; struct edge{ int t,n; edge(int t=0,int n=0):t(t),n(n){} }e[MAXN<<1]; int head[MAXN],tot; inline void AddEdge(int s,int t){ e[++tot]=edge(t,head[s]),head[s]=tot; e[++tot]=edge(s,head[t]),head[t]=tot; } char a[MAXN],b[MAXN]; int p[MAXN],dep[MAXN]; int last[MAXN][Alpha]; ll son[MAXN]; void dfs(int x){ rep(i,0,Alpha) last[x][i]=last[p[x]][i]; last[x][a[p[x]]-'a']=p[x]; for(int i=head[x],y;i;i=e[i].n) if((y=e[i].t)!=p[x]){ p[y]=x; dep[y]=dep[x]+1; son[x]|=1ll<<(a[y]-'a'); dfs(y); } } int n,m; int ans[MAXN]; const int SIZE=(1<<18)-1; struct hash{ ll set[MAXN]; int next[MAXN],id[MAXN]; int head[SIZE+1],tot; inline void insert(ll s,int Id){ int p=s&SIZE; set[++tot]=s,next[tot]=head[p]; id[tot]=Id,head[p]=tot; } inline void update(ll s){ int p=s&SIZE; for(int i=head[p];i;i=next[i]) if(set[i]==s)++ans[id[i]]; } }set; inline bool cmp(int a,int b){return dep[a]>dep[b];} inline void deal(int x){ static int q[Alpha+1]; rep(i,0,Alpha)q[i]=last[x][i]; sort(q,q+Alpha,cmp); ll S=1ll<<(a[x]-'a'); rep(i,0,Alpha){ if(S&son[x])break; if(!q[i])break; ll s=1ll<<(a[q[i]]-'a'); if(!(S&s)){ set.update(S); } S|=s; } if(S&son[x])return; set.update(S); } int main(){ freopen("loneliness.in","r",stdin); freopen("loneliness.out","w",stdout); Scan(n),Scan(m); scanf("%s",a+1); rep(i,1,n){ int u,v; Scan(u),Scan(v); AddEdge(u,v); } rep(i,1,m+1){ ll S=0; scanf("%s",b); int l=strlen(b); rep(j,0,l){ S|=1ll<<(b[j]-'a'); } set.insert(S,i); } dep[1]=1,dfs(1); rep(i,1,n+1)deal(i); rep(i,1,m+1)printf("%d\n",ans[i]); return 0; }
Swap
题意:
给出一个n*m(n<=103,m<=103)的黑白棋棋盘.
棋盘上有t(t<=105)个位置坏掉了。
除了坏掉的位置之外,每个位置上有且只有1颗棋子,且初始时白面向上。
你可以进行k(k<=109)次操作。
每次操作选择两颗棋子,将以它们为对角线的矩形内的所有棋子状态取反(不一定是主对角线).
问k次操作后,白面向上的棋子个数的期望值是多少。
分析:
由于期望具有线性性,我们可以分别计算每颗棋子的期望最后再求和。
假设某颗棋子在1次操作中被翻转的概率为p。
那么容易推出它k次操作后状态不变的概率为(1+(1-2*p)k)/2.
现在问题就在于如何快速求出p.
由容斥得(x,y)被翻转的情况有:
|([1,x]×[1,y],[x,y]×[n,m])|+|([x,n]×[1,y],[1,x]×[y,m])|-|([x,x]×[1,y],[x,x]×[y,m])|-|([1,x]×[y,y],[x,n]×[y,y])|+1
用前缀和优化就好了。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define g() getchar() template<class Q>void Scan(Q&x){ char c;while(c=g(),c<48||c>57); for(x=0;c>47&&c<58;c=g())x=10*x+c-48; } #define rep(i,a,b) for(int i=a;i<b;++i) const int MAXN=1005; int n,m,k,t; bool a[MAXN][MAXN]; int sum[MAXN][MAXN]; inline int Sum(int x1,int y1,int x2,int y2){ int res=sum[x2][y2]-sum[x2][y1-1]; return res-(sum[x1-1][y2]-sum[x1-1][y1-1]); } inline void Init(){ Scan(n),Scan(m),Scan(k),Scan(t); rep(i,1,n+1)rep(j,1,m+1)sum[i][j]=a[i][j]=1; rep(i,1,t+1){ int x,y; Scan(x),Scan(y); sum[x][y]=a[x][y]=0; } rep(i,1,n+1)rep(j,1,m+1)sum[i][j]+=sum[i][j-1]; rep(i,1,n+1)rep(j,1,m+1)sum[i][j]+=sum[i-1][j]; } typedef long double ld; typedef long long ll; inline ld qpow(ld x,int k){ ld ans=1; for(;k;x=x*x,k>>=1) if(k&1)ans=ans*x; return ans; } ld ans; inline void Solve(){ ll q=1ll*sum[n][m]*sum[n][m]; rep(i,1,n+1)rep(j,1,m+1){ if(!a[i][j])continue; ll p=1ll*Sum(1,1,i,j)*Sum(i,j,n,m)+1ll*Sum(1,j,i,m)*Sum(i,1,n,j); p-=1ll*Sum(i,1,i,j)*Sum(i,j,i,m)+1ll*Sum(1,j,i,j)*Sum(i,j,n,j); p=p<<1|1; ld pr=(ld)p/q; ans+=(qpow(1.-pr*2.,k)+1.)/2.; } printf("%.20f\n",(double)ans); } int main(){ freopen("swap.in","r",stdin); freopen("swap.out","w",stdout); Init(); Solve(); return 0; }
codeforces 442D Adam and Tree
题意:
给出一棵树,最开始只有1个节点。
每个时刻,这棵树会长出一个叶子。
树上每条边都有且只有1种颜色,并且同一种颜色的边都连通。
定义一个点到根的距离为该点到根的路径上的颜色种数。
求每个时刻树上最大距离的最小值。
分析:
考虑每次向上更新dp值,直到更新到根或者不能更新为止。
由树链剖分可知这样做的复杂度是O(nlogn)的。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int MAXN=1e6+5; int n; int dp[MAXN],p[MAXN]; int mx1[MAXN],mx2[MAXN]; int main(){ freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); scanf("%d",&n); puts("0"); for(int i=2;i<=n;++i){ scanf("%d",p+i); dp[i]=1; int cur=i; while(cur!=1){ int par=p[cur]; if(dp[cur]>mx1[par]){ mx2[par]=mx1[par]; mx1[par]=dp[cur]; } else if(dp[cur]>mx2[par]){ mx2[par]=dp[cur]; } cur=par; int res=max(mx1[cur],mx2[cur]+1); if(res==dp[cur])break; dp[cur]=res; } printf("%d\n",mx1[1]); } return 0; }
Link
题意:
有n(n<=20000)个小球,最开始都未被染色。
进行m(m<=20000)次操作,操作分为2种类型。
0.回到第x次操作的状态
1.将小球x和y所在的集合合并
然后进行k(k<=20000)次染色操作。
第i次染色操作将时间ti时xi所在连通块中最多zi个小球染色。
如果一个小球被染色了,那么任意时刻它都被染色了。
求最多能将多少小球染色。
分析:
很容易想到网络流的做法,但是一般的建图方法的点数和边数都无法接受。
考虑用可持久化的思想优化网络流建图。
首先处理连通性时我们会用到用主席树实现的可持久化并查集。
注意,这时不能使用路径压缩,要按秩合并来优化。
然后可以发现可持久化并查集就可以来优化建图了。
我们只考虑叶子。
S向最初版本的每个叶子连容量1的边。
每次更新时将历史版本向当前版本连容量为+∞的边;
合并时,向集合代表元连容量为+∞的边。
每次染色时,找到ti版本xi所在集合的代表元,向T连容量为zi的边。
然后跑最大流就行了。
#include <stdio.h> #include <string.h> #include <algorithm> #define S 1 #define T 2 #define inf 0x7f7f7f7f #define gap(i) e[i].c-e[i].f using namespace std; #define g() getchar() template<class Q>void Scan(Q&x){ char c; while(c=g(),c<48||c>57); for(x=0;c>47&&c<58;c=g())x=10*x+c-48; } #define rep(i,a,b) for(int i=a;i<b;++i) const int MAXN=80005; int Id=2; typedef struct node*star; struct node{ int f,id,sz; star ch[2]; }pool[MAXN*20]; star tail=pool; inline star query(star x,int l,int r,int pos){ while(l<r){ int mid=(l+r)>>1,d=pos>mid; if(d)l=mid+1; else r=mid; x=x->ch[d]; } return x; } int n,m,k,Head[MAXN]; star rt[MAXN]; struct edge{ int t,c,f,n; edge(int t=0,int c=0,int n=0): t(t),c(c),f(0),n(n){} }e[MAXN<<2]; int head[MAXN],tot=1; inline void AddEdge(int s,int t,int c=1){ e[++tot]=edge(t,c,head[s]),head[s]=tot; e[++tot]=edge(s,0,head[t]),head[t]=tot; } void build(star&x,int l,int r){ x=tail++; if(l==r){ x->f=l,x->sz=1; AddEdge(S,x->id=++Id); return; } int mid=(l+r)>>1; build(x->ch[0],l,mid); build(x->ch[1],mid+1,r); } void update(star&y,star x,int l,int r,int t,int f,int v){ y=++tail; *y=*x; if(l==r){ y->f=f,y->sz+=v; AddEdge(x->id,y->id=++Id,inf); return; } int mid=(l+r)>>1,d=t>mid; if(d)l=mid+1; else r=mid; update(y->ch[d],x->ch[d],l,r,t,f,v); } star find(int idx,int x){ star res=query(rt[idx],1,n,x); return res->f==x?res:find(idx,res->f); } inline void Init(){ Scan(n),Scan(m),Scan(k); build(rt[0],1,n); } inline void Merge(int idx,int x,int y){ star fx=find(idx,x); star fy=find(idx,y); if(fx->f==fy->f)return; if(fx->sz>fy->sz)swap(fx,fy); update(rt[idx],rt[idx],1,n,fx->f,fy->f,0); update(rt[idx],rt[idx],1,n,fy->f,fy->f,fx->sz); fx=query(rt[idx],1,n,fx->f); fy=query(rt[idx],1,n,fy->f); AddEdge(fx->id,fy->id,inf); } int d[MAXN],p[MAXN],q[MAXN]; int cur[MAXN],g[MAXN]; inline int isap(int s,int t,int n){ rep(i,1,n+1)d[i]=n; int tail=1; q[1]=t; d[t]=0; rep(h,1,tail+1){ int x=q[h]; ++g[d[x]],cur[x]=head[x]; for(int i=head[x],y;i;i=e[i].n) if(!e[i].c&&d[y=e[i].t]==n){ d[y]=d[x]+1; q[++tail]=y; } } int flow=0,x=s,a,f; while(d[s]<n){ if(x==t){ a=inf; for(int i=t;i!=s;i=e[p[i]^1].t) a=min(a,gap(p[i])); for(int i=t;i!=s;i=e[p[i]^1].t) e[p[i]].f+=a,e[p[i]^1].f-=a; flow+=a; x=s; } f=1; for(int&i=cur[x];i;i=e[i].n) if(gap(i)&&d[x]==d[e[i].t]+1){ f=0; p[x=e[i].t]=i; break; } if(f){ if(!(--g[d[x]]))break; f=n-1; for(int i=head[x];i;i=e[i].n) if(gap(i))f=min(f,d[e[i].t]); ++g[d[x]=f+1]; cur[x]=head[x]; if(x!=s)x=e[p[x]^1].t; } } return flow; } inline void Solve(){ rep(i,1,m+1){ rt[i]=rt[i-1]; int t,x,y; Scan(t),Scan(x); if(t)Scan(y),Merge(i,x,y); else rt[i]=rt[x]; } rep(i,0,k){ int x,y,w; Scan(x),Scan(y),Scan(w); star f=find(x,y); AddEdge(f->id,T,w); } printf("%d\n",isap(S,T,Id)); } int main(){ freopen("link.in","r",stdin); freopen("link.out","w",stdout); Init(); Solve(); return 0; }
Run
题意:
给出一张图(点数为n,边数为m)的一个生成树和一些非树边。
每条非树边有一个代价w,表示炸毁这条边需要付出的代价。
求炸毁一些非树边使得图中不存在偶环所付出的最小代价。
特别地,生成树上点的度数不超过10.
数据范围:n<=1000,m<=4000
分析:
首先如果一条非树边跨越了树上奇数条边,则这条边必须炸毁。
剩下的边,如果任意两条边所跨越的树上路径有公共边,则会存在偶环。
考虑到每个节点的儿子最多只有10个,我们可以考虑状压dp.
令f[x][s]表示以x为根的子树,只考虑集合s中儿子时能保留的边的最大权值。
令g[x][i]表示以x为根的子树,不考虑x的第i个儿子时能保留的边的最大权值。
每条非树边,只在它的端点的lca处考虑影响。
为了让路径不重复,则该非树边所覆盖的路径都不能被选。
暴力从一个端点到lca统计不考虑路径上的边时的最大权值。
再与该非树边没有覆盖的部分一起更新答案。
复杂度O(n*210+n*m)
#include <vector> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define g() getchar() template<class Q>void Scan(Q&x){ char c;int f=1; while(c=g(),c<48||c>57)if(c=='-')f=-1; for(x=0;c>47&&c<58;c=g())x=10*x+c-48; x*=f; } #define Max(x,y) if(x<y)x=y #define Min(x,y) if(x>y)x=y #define pb push_back #define rep(i,a,b) for(int i=a;i<b;++i) const int MAXN=1005; const int MAXS=1<<10; int n,m; struct edge{ int t,n; edge(int t=0,int n=0):t(t),n(n){} }e[MAXN<<1]; int head[MAXN],tot; inline void AddEdge(int s,int t){ e[++tot]=edge(t,head[s]),head[s]=tot; e[++tot]=edge(s,head[t]),head[t]=tot; } int p[MAXN],dep[MAXN]; vector<int>ch[MAXN],qy[MAXN]; void dfs(int x){ for(int i=head[x],y;i;i=e[i].n) if((y=e[i].t)!=p[x]){ p[y]=x; dep[y]=dep[x]+1; ch[x].pb(y); dfs(y); } } inline int lca(int x,int y){ if(dep[x]>dep[y])swap(x,y); while(dep[y]>dep[x])y=p[y]; while(p[x]!=p[y])x=p[x],y=p[y]; return x==y?x:p[x]; } struct edge1{ int u,v,w; inline void scan(){ Scan(u),Scan(v),Scan(w); } }tmp[MAXN*5]; int ans; inline void Init(){ Scan(n),Scan(m); rep(i,1,m+1){ tmp[i].scan(); ans+=tmp[i].w; if(!tmp[i].w){ AddEdge(tmp[i].u,tmp[i].v); } } dfs(1); rep(i,1,m+1){ if(!tmp[i].w)continue; int u=tmp[i].u,v=tmp[i].v; int Lca=lca(u,v); if((dep[u]+dep[v]-dep[Lca]*2)&1)continue; qy[Lca].pb(i); } } int dp[MAXN]; int f[MAXN][MAXS],g[MAXN][10]; inline int swim(int x,int par,int&res){ int ban=-1; while(x!=par){ if(~ban)res+=g[x][ban]; else res+=dp[x]; int s=ch[p[x]].size(); rep(i,0,s) if(x==ch[p[x]][i]){ ban=i; break; } x=p[x]; } return ban; } void tree_dp(int x){ for(int i=head[x],y;i;i=e[i].n) if((y=e[i].t)!=p[x])tree_dp(y); int s=ch[x].size(); int maxs=1<<s; rep(set,0,maxs){ int res=0; rep(i,0,s) if(set>>i&1){ res+=dp[ch[x][i]]; } Max(f[x][set],res); } int c=qy[x].size(); rep(i,0,c){ int u=tmp[qy[x][i]].u; int v=tmp[qy[x][i]].v; int res=tmp[qy[x][i]].w; int bu=max(0,1<<swim(u,x,res)); int bv=max(0,1<<swim(v,x,res)); rep(set,0,maxs){ if(bu&&(set&bu)==bu||bv&&(set&bv)==bv) continue; int ns=set^bu^bv; Max(f[x][ns],res+f[x][set]); } } rep(set,0,maxs){ rep(i,0,s) if(!(set>>i&1)){ Max(g[x][i],f[x][set]); } Max(dp[x],f[x][set]); } } inline void Solve(){ tree_dp(1); ans-=dp[1]; printf("%d\n",ans); } int main(){ freopen("run.in","r",stdin); freopen("run.out","w",stdout); Init(); Solve(); return 0; }