「SOL」Tug of War(洛谷)

至理名言「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;
}

當時迷霧正濃 謊稱歌頌
斥退肆虐的獸 幸得榮寵
笑我成瘋成魔 吶喊洶涌
情深索性無終 登上靈鷲

——《你是我高不可攀的夢》By 蒼穹/papaw泡泡

> Link 你是我高不可攀的夢-Bilibili

相關文章
相關標籤/搜索