至理名言「deadline 是第一辈子产力」ide
有 \(2n\) 我的,分红两个队(A,B)拔河。其中第 \(i\) 我的若参加 A 队,则只能站在 A 队的 \(a_i\) 位置;若参加 B 队,则只能站在 \(b_i\) 位置;而且他的力量为 \(k_i\)。优化
要求两队都恰有 \(n\) 我的,同一个位置不能站多我的,而且两队的力量之差不超过 \(K\)。spa
求是否存在知足要求的方案。code
数据规模:\(1\le n\le3\times10^4\),\(k_i\le20\)ci
无论「力量差不超过 \(K\)」的限制,先看看何时存在合法的分组。get
作过相似的题,把一我的看做一条边 \((a_i,b_i+n)\),能够转化为图论模型。input
那么在这个模型中,点表示队伍中的位置,边表示人。一条边只能“占据”其端点的位置,因而能够转化为「给边定向,使得每一个点恰有一个入度」的问题。string
首先必须知足每一个连通块的点数等于边数,这意味着每一个连通块恰有一个环(基环树)。it
再推导一下。咱们从基环树上总度数为 \(1\) 的点开始给边定向,发现与它链接的惟一一条边的方向是固定指向它的。因而定向后删去该点和边,继续处理子问题。io
因而咱们能够发现——基环树上非环上边的方向是固定的。而环上的边则显然存在两种方案(“顺时针”和“逆时针”)。
咱们不妨计算「A 队总力量 - B 队总力量」,则定义一个有向边的边权——若指向 \(a_i\),边权为 \(+k_i\);指向 \(b_i\) 则为 \(-k_i\)。
先随便给环定个向,对每一个基环树算出其环的权 \(c\) 和非环边的权 \(l\),则易得整个基环树的权只可能为 \(l\pm c\)。
这样咱们作一个「0-1背包」,并且注意到 dp 值是 bool
,常规操做用 bitset
进行优化,记 \(S=20n\),则复杂度 \(\mathcal{O}(\frac{Sn}{w})\)。
直接这么作不开 O2 是卡不过的,考虑继续优化。
注意到上述背包的全部物品的大小总和不超过 \(S\),那么这意味着大小不一样的物品总数是 \(\mathcal{O}(\sqrt S)\) 的——大小超过 \(\sqrt S\) 的物品至多有 \(\sqrt S\) 种,小于 \(\sqrt S\) 的物品原本就只有 \(\sqrt{S}\) 种。
因而将「0-1背包」转化为「多重背包」,最后利用二进制分组从新转化为物品数为 \(\mathcal{O}(\sqrt{S}\log n)\) 的「0-1背包」。
再次利用 bitset
将时间复杂度降至 \(\mathcal{O}(\frac{S^{1.5}\log n}{w})\)。的确是一道分析和优化思路都很清晰的好题。
/*Lucky_Glass*/ #include<bitset> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; inline int rin(int &r){ int b=1,c=getchar();r=0; while(c<'0' || '9'<c) b=c=='-'?-1:b,c=getchar(); while('0'<=c && c<='9') r=(r<<1)+(r<<3)+(c^'0'),c=getchar(); return r*=b; } const int N=3e4+10; #define con(type) const type & struct Dsu{ int fa[N<<1]; bool tag[N<<1]; inline int findFa(con(int)u){return fa[u]==u?u:fa[u]=findFa(fa[u]);} inline bool merge(int u,int v){ u=findFa(u),v=findFa(v); if(u==v){ if(tag[u]) return false; return tag[u]=true; } if(tag[u] && tag[v]) return false; fa[u]=v,tag[v]|=tag[u]; return true; } void init(con(int)n){ for(int i=1;i<=n;i++) fa[i]=i,tag[i]=false; } }ds; struct Graph{ int head[N<<1],to[N<<2],nxt[N<<2],len[N<<2],ncnt; Graph(){ncnt=1;} void addEdge(con(int)u,con(int)v,con(int)l){ int p=++ncnt,q=++ncnt; to[p]=v,nxt[p]=head[u],len[p]=l,head[u]=p; to[q]=u,nxt[q]=head[v],len[q]=l,head[v]=q; } inline int operator [](con(int)u){return head[u];} }gr; int n,bonk,rcir,rlis,nval_bin,nval; int dep[N<<1],val_bin[N],val[N]; bitset<N*40> dp[2]; bool covered[N<<1]; bool searchDFS(con(int)u,con(int)las_edg){ bool oncir=false,fix_end=false; for(int it=gr[u];it;it=gr.nxt[it]){ int v=gr.to[it]; if((it>>1)==las_edg) continue; if(dep[v]){ if(dep[v]<dep[u]){ oncir=true; covered[v]=true; if(v>n) rcir+=gr.len[it]; else rcir-=gr.len[it]; } else fix_end=true; continue; } dep[v]=dep[u]+1; if(searchDFS(v,it>>1)){ oncir=true; if(v>n) rcir+=gr.len[it]; else rcir-=gr.len[it]; } else{ if(covered[v]){ covered[u]=true; if(u>n) rlis+=gr.len[it]; else rlis-=gr.len[it]; } else if(v>n) rlis+=gr.len[it]; else rlis-=gr.len[it]; } } return !fix_end && oncir; } int main(){ // freopen("input.in","r",stdin); rin(n),rin(bonk); ds.init(n<<1); for(int i=1,li,ri,ki;i<=2*n;i++){ rin(li),ri=rin(ri)+n,rin(ki); if(!ds.merge(li,ri)){printf("NO\n");return 0;} gr.addEdge(li,ri,ki); } for(int i=1;i<=2*n;i++) if(ds.fa[i]==i && !ds.tag[i]){ printf("NO\n"); return 0; } int ini_val=0; for(int i=1;i<=2*n;i++) if(!dep[i]){ dep[i]=1; rlis=rcir=0,searchDFS(i,-1); // printf("? %d %d\n",rlis+rcir,rlis-rcir); if(rcir>0){ ini_val+=rlis-rcir; val_bin[++nval_bin]=rcir<<1; } else{ ini_val+=rlis+rcir; val_bin[++nval_bin]=(-rcir)<<1; } } sort(val_bin+1,val_bin+1+nval_bin); for(int i=1;i<=nval_bin;i++){ int ii=i,siz; while(ii<nval_bin && val_bin[ii+1]==val_bin[i]) ii++; siz=ii-i+1; for(int j=1;j<=siz;j<<=1){ val[++nval]=j*val_bin[i]; siz-=j; } if(siz) val[++nval]=siz*val_bin[i]; i=ii; } dp[0][0]=1; int I=1; for(int i=1;i<=nval;i++,I^=1) dp[I]=dp[!I]|(dp[!I]<<val[i]); for(int i=max(-bonk-ini_val,0);i<=min(bonk-ini_val,40*N-1);i++) if(dp[I][i]){ printf("YES\n"); return 0; } printf("NO\n"); return 0; }