图论1(许廷强)做题总结

CF1239E. Turtle

神秘贪心+dp题。
首先假设第一行和第二行所选的数已经定了,只用考虑摆放顺序。
\(w_i\)为从第\(i\)行拐入第二行的答案。
对于第一行,假设有\(a_{1,i}>a_{1,i+1}\)时,交换\(a_{1,i}\)\(a_{1,i+1}\),发现此时\(w_{1\sim i-1}\)不变,\(s_{i+1\sim n}\)也不变,只有\(w_i\)变小,所以答案会变小或不变,总之不会变劣。
所以第一行一定是\(a_{1,1}\)\(a_{1,n}\)从小到大排序,同理可得第二行一定是从大到小排序。
接下来可以研究\(w_i\)的差分数组\(c_i\)的单调性。
\(c_i=a_{1,i}-a_{2,i-1}\),所以\(c_i\)单调递增,所以\(w_i\)具有凸性,且顶点处为最小值,所以最大值仅会出现在\(w_1\)\(w_n\)之中。
发现\(w_1=\sum_{i=1}^{n}a_{2,i}+a_{1,1}=\sum_{i=1}^{2}\sum_{j=1}^{n}a_{i,j}-\sum_{i=1}^{n}a_{1,i}+a_{1,1}\)\(w_n=\sum_{i=1}^{n}a_{1,i}+a_{2,n}\)都只和第一列的和有关,故可以用DP来求出第一行数的所有可能以及方案,复杂度\(O(n\sum a)\)但我们还需要枚举\(a_{1,1}\)\(a_{2,n}\)复杂度需要多加\(n^2\),无法通过。
此时我们可以发现\(a_{1,1}\)\(a_{2,n}\)两个数在每一种路径中都会经过,所以取最小的两个数一定不劣。那么总时间就可以在\(O(n\sum a)\)
代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=30,inf=1e9;
int a[N<<1],dp[N][N<<16],pre[N][N<<16],vis[N<<1],ans[2][N];
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int n;fin>>n;
	for(int i=1;i<=n*2;i++) fin>>a[i];
	sort(a+1,a+1+2*n);
	dp[1][a[1]]=1;pre[1][a[1]]=1;
	for(int i=3;i<=2*n;i++)
	{
		for(int j=n;j>=2;j--)
		{
			for(int k=a[i];k<=a[2*n]*n;k++)
			{
				if(dp[j][k]) continue;
				dp[j][k]|=dp[j-1][k-a[i]];
				if(dp[j-1][k-a[i]]) pre[j][k]=i;
			}
		}
	}
	int sum=0,mn=inf,idx=0;
	for(int i=1;i<=2*n;i++) sum+=a[i];
	for(int i=1;i<=min(n*a[n*2],sum);i++)
	{
		if(!dp[n][i]) continue;
		int val=max(i+a[2],sum-i+a[1]);
		if(val<mn) idx=i,mn=val;
	}
	for(int i=n;i>=1;i--)
	{
		vis[pre[i][idx]]=1;
		ans[0][i]=a[pre[i][idx]];
		idx-=a[pre[i][idx]];
	}
	int now=n;
	for(int i=1;i<=n*2;i++)
	{
		if(!vis[i]) ans[1][now--]=a[i];
	}
	for(int i=0;i<=1;i++)
	{
		for(int j=1;j<=n;j++) fout<<ans[i][j]<<" ";
		fout<<"\n";
	}
	return 0;
}

CF521E Cycling City

首先我们会发现符合条件的点对的样子大概是环上加一条边的这条边上的两个点。对于这种带环的图论题,第一反应是找出一个生成树然后再生成树树跑路径,手玩几组会发现如果两个路径有边的交集,那么就可以构造出一组解。大概长这个样子
![]( https://cdn.luogu.com.cn/upload/image_hosting/viqkh3gw.png?x-oss-process=image/resize ,m_lfit,h_1000,w_1000)
其中绿色为树边,红黄蓝三色为三条路径,及重合树边一条,绕到第一条非树边上的一条,绕到第二条非树边上的一条。
代码实现是可以直接暴力,因没有重合时复杂度和枚举每条树边的复杂度相等,有重合就可以直接统计答案了。
代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=200010;
int f[N],h[N],tot,vis[N],d[N],F[N],col[N],ff[20][N];
struct node
{
	int nxt,to;
} e[N<<1];
struct edge
{
	int u,v;
} E[N];
vector<int> huan[N];
void add(int u,int v)
{
	tot++;
	e[tot].to=v;
	e[tot].nxt=h[u];
	h[u]=tot;
}
int fnd(int x)
{
	if(f[x]==x) return x;
	return f[x]=fnd(f[x]);
}
void dfs(int u,int fa)
{
	d[u]=d[fa]+1;F[u]=fa;
	ff[0][u]=fa;
	for(int i=1;i<=19;i++) ff[i][u]=ff[i-1][ff[i-1][u]];
	for(int i=h[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
	}
}
int get_lca(int u,int v)
{
	if(d[u]<d[v]) swap(u,v);
	for(int i=19;i>=0;i--)
	{
		if(d[ff[i][u]]>=d[v]) u=ff[i][u];
	}
	if(u==v) return u;
	for(int i=19;i>=0;i--)
	{
		if(ff[i][u]!=ff[i][v]) u=ff[i][u],v=ff[i][v];
	}
	return ff[0][u];
}
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int n,m;fin>>n>>m;
	for(int i=1;i<=m;i++) fin>>E[i].u>>E[i].v;
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=m;i++)
	{
		int u=E[i].u,v=E[i].v;
		if(fnd(u)!=fnd(v))
		{
			vis[i]=1;
			f[fnd(u)]=fnd(v);
			add(u,v);add(v,u);
		}
	}
	for(int i=1;i<=n;i++) if(f[i]==i) dfs(i,0);
	vector<int> tmp,tmp2;
	for(int i=1;i<=m;i++)
	{
		if(!vis[i])
		{
			int u=E[i].u,v=E[i].v,lca=get_lca(u,v);
			int idx=0,lst=0;
			while(u!=lca)
			{
				huan[i].push_back(u);
				if(col[u] && !idx) idx=col[u];
				if(col[u] && col[u]==idx) tmp.push_back(u),lst=F[u];
				col[u]=i;
				u=F[u];
			}
			int fl=0;
			if(lst) tmp.push_back(lst);
			if(lst==lca) fl=1;
			huan[i].push_back(lca);
			vector<int> tmp3;
			lst=0;
			while(v!=lca)
			{
				tmp3.push_back(v);
				if(col[v] && !idx) idx=col[v];
				if(col[v] && col[v]==idx) tmp2.push_back(v),lst=F[v];
				col[v]=i;
				v=F[v];
			}
			if(lst)
			{
				if(lst!=lca) tmp2.push_back(lst);
				else if(!fl) tmp.push_back(lst);
			}
			reverse(tmp3.begin(),tmp3.end());
			for(int j:tmp3) huan[i].push_back(j);
			if(idx)
			{
				fout<<"YES\n";
				reverse(tmp2.begin(),tmp2.end());
				for(int j:tmp2) tmp.push_back(j);
				fout<<tmp.size()<<" ";
				for(int j:tmp) fout<<j<<" ";
				fout<<"\n";
				vector<int> ans;
				u=tmp[0],v=tmp[(int)tmp.size()-1];
				reverse(huan[i].begin(),huan[i].end());
				fl=0;
				for(int j:huan[i])
				{
					if(j==u) fl=1;
					if(fl) ans.push_back(j);
				}
				for(int j:huan[i])
				{
					ans.push_back(j);
					if(j==v) break;
				}
				fout<<ans.size()<<" ";
				for(int j:ans) fout<<j<<" ";
				fout<<"\n";
				int idx1=0,idx2=0;
				for(int j=0;j<(int)huan[idx].size();j++)
				{
					if(huan[idx][j]==u) idx1=j;
					if(huan[idx][j]==v) idx2=j;
				}
				if(idx1<idx2) reverse(huan[idx].begin(),huan[idx].end());
				vector<int> ().swap(ans);
				fl=0;
				for(int j:huan[idx])
				{
					if(j==u) fl=1;
					if(fl) ans.push_back(j);
				}
				for(int j:huan[idx])
				{
					ans.push_back(j);
					if(j==v) break;
				}
				fout<<ans.size()<<" ";
				for(int j:ans) fout<<j<<" ";
				fout<<"\n";
				return 0;
			}
		}
	}
	fout<<"NO";
	return 0;
}

CF1142E Pink Floyd

这是啥必做的交互吗。

先考虑没有粉边的情况,我们可以维护一个可以成为答案的集合,每次从集合中取出两个数,询问两个数之间的边的方向,然后将出点加回队列,将入点删掉。直到最后只剩一个点。差不多就是并查集的思路。

接下来考虑扩展此算法,先考虑 \(dag\) 上的情况。维护的集合要保证粉边的入度为0,每次删除时,要加入新的入度为0的点。

继续扩展到有环的情况,我们发现只需要从图中提取出一个 \(dag\) 就可以,以为题目没要求答案最小。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
const int N=100010;
int st[N],tp,ru[N],fl[N],vis[N];
vector<int> e[N],g[N];
void dfs(int u)
{
	vis[u]=fl[u]=1;
	for(int v:g[u])
	{
		if(!fl[v]) e[u].push_back(v),ru[v]++;
		if(!vis[v]) dfs(v);
	}
	fl[u]=0;
}
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int n,m;cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;cin>>u>>v;
		g[u].push_back(v);
	}
	for(int i=1;i<=n;i++) if(!vis[i]) dfs(i);
	for(int i=1;i<=n;i++) if(!ru[i]) st[++tp]=i;
	while(tp>1)
	{
		int u=st[tp-1],v=st[tp];tp-=2;
		cout<<"? "<<u<<" "<<v<<"\n";
		fflush(stdout);
		int op;cin>>op;
		if(op==0)
		{
			st[++tp]=v;
			for(int i:e[u])
			{
				ru[i]--;
				if(!ru[i]) st[++tp]=i;
			}
		}
		else
		{
			st[++tp]=u;
			for(int i:e[v])
			{
				ru[i]--;
				if(!ru[i]) st[++tp]=i;
			}
		}
	}
	cout<<"! "<<st[1]<<"\n";
	fflush(stdout);
	return 0;
}
/*
从特殊化到一般化的转变
无粉边->有粉边 
保留只保留dag,其他的边无用 
*/

CF611H New Year and Forgotten Tree

可以将所有数分成 \(m=\lceil log_{10} n\rceil+1\) 个种类,每一个种类中选取一个关键点,不妨设为 \(10^{k-1}\) ,则发现一定有一种构造方案为先在关键点中构造出一棵树,然后其他点向关键点连边,证明可以考虑调整法(若有一条连边不含关键点,则一定可以将其调整为含关键点)。生成树可以考虑 Prüfer 序列,复杂度\(O(m^{m-2})\)。连接关键点可以考虑网络流跑匹配,复杂度\(O(poly(m))\) 总复杂度很低,可过。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=200010,inf=1e9;
int n,m,c[10][10],a[10],totedge=1,h[100],S,T,d[N],cnt,b[10],ansf,dis[N],cur[100];
pair<int,int> www[100];
struct edge
{
	int to,nxt,w;
} e[300];
il void add(int u,int v,int w)
{
	totedge++;
	e[totedge].to=v;
	e[totedge].nxt=h[u];
	e[totedge].w=w;
	h[u]=totedge;
}
il void add_edge(int u,int v,int w)
{
	add(u,v,w);add(v,u,0);
}
il vector<pair<int,int> > query_path()
{
	vector<pair<int,int> > ret;
	priority_queue<int,vector<int>,greater<int> > pq;
	for(int i=1;i<=m-2;i++) d[a[i]]++;
	for(int i=1;i<=m;i++) if(!d[i]) pq.push(i);
	for(int i=1;i<=m-2;i++)
	{
		int v=pq.top();pq.pop();
		ret.push_back({a[i],v});
		d[a[i]]--;
		if(!d[a[i]]) pq.push(a[i]);
	}
	int u=pq.top();pq.pop();int v=pq.top();pq.pop();
	ret.push_back({u,v});
	for(auto &i:ret) if(i.first>i.second) swap(i.first,i.second);
	return ret;
}
il bool bfs()
{
	queue<int> q;
	for(int i=1;i<=cnt;i++) dis[i]=0,cur[i]=h[i];
	dis[S]=1;q.push(S);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=h[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(!dis[v] && e[i].w>0)
			{
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[T]>0;
}
il int dfs(int u,int val)
{
	if(u==T) return val;
	int w=0;
	for(int& i=cur[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(e[i].w>0 && dis[v]==dis[u]+1)
		{
			int x=dfs(v,min(e[i].w,val-w));
			if(x>0) w+=x,e[i].w-=x,e[i^1].w+=x;
			if(w==val) return w;
		}
	}
	return w;
}
il void Dinic()
{
	ansf=0;
	while(bfs()) ansf+=dfs(S,inf);
}
il void Dfs(int now)
{
	if(now==m-1)
	{
		vector<pair<int,int> > tmp=query_path();
		int fl=1;
		for(auto i:tmp)
		{
			if(!c[i.first][i.second]) fl=0;
			c[i.first][i.second]--;
		}
		if(!fl)
		{
			for(auto i:tmp) c[i.first][i.second]++;
			return;
		}
		cnt=m*(m+1)/2+m+2;S=cnt-1,T=cnt;
		totedge=1;
		for(int i=1;i<=cnt;i++) h[i]=0;
		for(int i=1;i<=m;i++) add_edge(S,i,b[i]-1);
		int now=m;
		for(int i=1;i<=m;i++)
		{
			for(int j=i;j<=m;j++)
			{
				now++;
				add_edge(i,now,inf);
				if(i!=j) add_edge(j,now,inf);
				add_edge(now,T,c[i][j]);
				www[now]={i,j};
			}
		}
		Dinic();
		if(ansf==n-m)
		{
			vector<pair<int,int> > ans;
			for(auto i:tmp) ans.push_back({(int)pow(10,i.first-1),(int)pow(10,i.second-1)});
			for(int i=1;i<=m;i++)
			{
				int now=pow(10,i-1)+1;
				for(int j=h[i];j;j=e[j].nxt)
				{
					int v=e[j].to;
					if(v==S) continue;
					int val=e[j^1].w;
					int idx=0;
					if(www[v].first==i) idx=www[v].second;
					else idx=www[v].first;
					idx=pow(10,idx-1);
					for(int k=now;k<=now+val-1;k++) ans.push_back({k,idx});
					now+=val;
				}
			}
			for(auto i:ans) fout<<i.first<<" "<<i.second<<"\n";
			exit(0);
		}
		for(auto i:tmp) c[i.first][i.second]++;
		return;
	}
	for(int i=1;i<=m;i++)
	{
		a[now]=i;
		Dfs(now+1);
	}
}
signed main()
{
//	freopen("1.in","r",stdin);
//	freopen("chj.out","w",stdout);
	fin>>n;m=(int)log10(n)+1;
	if(m==1)
	{
		for(int i=1;i<n;i++) fout<<i<<" "<<i+1<<"\n";
		return 0;
	}
	for(int i=1;i<m;i++) b[i]=9*pow(10,i-1);
	b[m]=n-pow(10,m-1)+1;
	for(int i=1;i<n;i++)
	{
		string s1,s2;fin>>s1>>s2;
		if(s1.size()>s2.size()) swap(s1,s2);
		c[s1.size()][s2.size()]++;
	}
	Dfs(1);
	fout<<-1;
	return 0;
}
/*
又是构造,烦死了,哼 
先构造出关键点,发现其他边肯定向关键点连
然后跑个二分图匹配就完了 
*/

CF1305G Kuroni and Antihype - 洛谷

考虑建模,将A邀请B看成是A连向了一条向B的有向边,边权为 \(a_i\) ,发现这样连边会连出一个外向树森林,森林较难处理,考虑建立一个虚点 权值为 \(0\) ,然后将这个点向其他的根节点连边。在转化有向边,发现可以将边权转化为连接的两点点权之和,此时的答案为边权和减点权和,由于点权和不变,所以相当于求原图的最小生成树。可以使用 \(Boruvka\) 轻松求出。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=200010,V=(1<<18)-1,inf=1e9;
int f[N],a[N],mx[V+10],imx[V+10],cmx[V+10],icmx[V+10],val[N],to[N];
vector<int> b[V+10];
int fnd(int x)
{
	if(f[x]==x) return x;
	return f[x]=fnd(f[x]);
}
void merge(int x,int y)
{
	int fx=fnd(x),fy=fnd(y);
	if(fx==fy) return;
	f[fy]=fx;
}
void upd(int i,int idx,int w)
{
	if(w>mx[i])
	{
		if(idx!=imx[i])
		{
			icmx[i]=imx[i],cmx[i]=mx[i];
			imx[i]=idx,mx[i]=w;
		}
		else mx[i]=w;
	}
	else if(w>cmx[i] && idx!=imx[i])
	{
		icmx[i]=idx,cmx[i]=w;
	}
}
void solve()
{
	for(int i=0;i<=V;i++) cmx[i]=mx[i]=imx[i]=icmx[i]=-inf;
	for(int i=0;i<=V;i++)
	{
		for(int j:b[i]) upd(i,fnd(j),i);
		if(i)
		{
			for(int j=0;j<=17;j++)
			{
				if(i>>j&1)
				{
					int s=i^(1<<j);
					upd(i,imx[s],mx[s]);
					upd(i,icmx[s],cmx[s]);
				}
			}
		}
	}
}
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int n;fin>>n;
	int ans=0,cnt=0;
	for(int i=1;i<=n;i++) fin>>a[i],f[i]=i,ans-=a[i],b[a[i]].push_back(i);
	f[n+1]=n+1;b[0].push_back(n+1);
	while(cnt<n)
	{
		solve();
		for(int i=1;i<=n+1;i++) val[i]=to[i]=0;
		for(int i=1;i<=n+1;i++)
		{
			int tmp=V^a[i],fi=fnd(i);
			if(imx[tmp]==fi)
			{
				if(cmx[tmp]+a[i]>val[fi])
				{
					val[fi]=cmx[tmp]+a[i];
					to[fi]=icmx[tmp];
				}
			}
			else
			{
				if(mx[tmp]+a[i]>val[fi])
				{
					val[fi]=mx[tmp]+a[i];
					to[fi]=imx[tmp];
				}
			}
		}
		for(int i=1;i<=n+1;i++)
		{
			if(f[i]==i)
			{
				int idx=to[i];
				if(fnd(i)!=fnd(idx))
				{
					f[fnd(i)]=fnd(idx);
					cnt++;
					ans+=val[i];
//					fout<<i<<" "<<idx<<" "<<val[i]<<"\n";
				}
			}
		}
	}
	fout<<ans;
	return 0;
}
/*
转化思想,见到森林可以想一想能不能把森林转成树(建虚点) 
*/

[ ARC103F ] Distance Sums

考虑构造差值,将所有 \(D_i\) 从大到小排序,则考虑从前往后构造,发现第1个点一定是叶子,然后通过差值找到该点的父亲,同时更新 \(siz\) ,注意最后要在验证一下答案正确性,因为我们构造的是差值,所以每个位上的值可能不一样。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=100010;
int tot,siz[N],d[N],sum[N],n;
unordered_map<int,int> mp;
vector<int> g[N];
struct edge
{
	int u,v;
} e[N];
struct node
{
	int idx,d;
} w[N];
bool cmp(node x,node y)
{
	return x.d>y.d;
}
il void get_siz(int u,int fa)
{
	siz[u]=1;
	for(int v:g[u])
	{
		if(v==fa) continue;
		get_siz(v,u);
		siz[u]+=siz[v];
		sum[u]+=sum[v]+siz[v];
	}
}
signed main()
{
//	freopen("hack.in","r",stdin);
//	freopen("chj.out","w",stdout);
	fin>>n;
	for(int i=1;i<=n;i++)
	{
		fin>>w[i].d;w[i].idx=i;
		d[i]=w[i].d;
	}
	sort(w+1,w+1+n,cmp);
	for(int i=1;i<=n;i++) mp[w[i].d]=i,siz[i]=1;
	for(int i=1;i<n;i++)
	{
		int val=w[i].d+siz[i]-(n-siz[i]);
		if(mp.find(val)==mp.end() || val>=w[i].d)
		{
			fout<<-1;
			return 0;
		}
		int idx=mp[val];
		e[++tot]={w[i].idx,w[idx].idx};
		siz[idx]+=siz[i];
	}
	for(int i=1;i<n;i++)
	{
		g[e[i].u].push_back(e[i].v);
		g[e[i].v].push_back(e[i].u);
	}
	get_siz(1,0);
	if(sum[1]!=d[1])
	{
		fout<<-1;
		return 0;
	}
	for(int i=1;i<n;i++) fout<<e[i].u<<" "<<e[i].v<<"\n";
	return 0;
}

[AGC025E] Walking on a Tree

这是啥必做的构造吗

首先发现一定可以构造出一种方案顶到答案上界。

然后考虑构造,先考虑做路径覆盖,然后考虑每条边都被覆盖偶数次的情形,则每条路径的每个端点的度数均为偶数,则可以考虑构造出出度和入度都相等的情况。可以使用欧拉回路个每条路径定向。

至于其余情况,我们发现可以构造出出度和入度只差一的情况,所以可以考虑补一些边,补边的话可以从叶子开始连边,对于每一个度数为奇数的点,可以向其子树内度数为奇数或祖先度数为奇数的点两边。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=2010;
int h[N],top[N],siz[N],son[N],d[N],tot=1,f[N],du[N],cur[N],val[N],c[N],ans;
vector<int> g[N];
struct edge
{
	int nxt,to,idx,vis;
} e[N<<2];
struct path
{
	int u,v;
} E[N];
void add_edge(int u,int v,int idx)
{
	tot++;
	e[tot].to=v;
	e[tot].nxt=h[u];
	e[tot].idx=idx;
	h[u]=tot;
}
void get_son(int u,int fa)
{
//	cerr<<u<<"\n";
	f[u]=fa;d[u]=d[fa]+1;
	siz[u]=1;
	for(int v:g[u])
	{
		if(v==fa) continue;
		get_son(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
		if(du[v])
		{
			du[u]^=1;
			du[v]^=1;
			add_edge(u,v,-1);
			add_edge(v,u,-1);
		}
	}
}
void get_top(int u,int t)
{
	top[u]=t;
	if(son[u]) get_top(son[u],t);
	for(int v:g[u])
	{
		if(v==f[u] || v==son[u]) continue;
		get_top(v,v);
	}
}
int get_lca(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(d[top[u]]<d[top[v]]) swap(u,v);
		u=f[top[u]];
	}
	if(d[u]<d[v]) swap(u,v);
	return v;
}
void get_oula(int u)
{
	for(int& i=cur[u];i;i=e[i].nxt)
	{
		if(e[i].vis) continue;
		int v=e[i].to;
		if(e[i].idx!=-1) val[e[i].idx]=(i&1);
		e[i].vis=e[i^1].vis=1;
		get_oula(v);
	}
}
void get_ans(int u)
{
	for(int v:g[u])
	{
		if(v==f[u]) continue;
		get_ans(v);
		c[u]+=c[v];
	}
	ans+=min(2,c[u]);
}
signed main()
{
//	freopen("in71.in","r",stdin);
//	freopen("chj.out","w",stdout);
	int n,m;fin>>n>>m;
	for(int i=1;i<n;i++)
	{
		int u,v;fin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	for(int i=1;i<=m;i++)
	{
		int u,v;fin>>u>>v;
		E[i]={u,v};
		add_edge(u,v,i);
		add_edge(v,u,i);
		du[u]^=1,du[v]^=1;
	}
	get_son(1,0);get_top(1,1);
	for(int i=1;i<=n;i++) cur[i]=h[i];
	for(int i=1;i<=n;i++)
	{
		if(cur[i]) get_oula(i);
	}
	for(int i=1;i<=m;i++)
	{
		int u=E[i].u,v=E[i].v;
		int lca=get_lca(u,v);
		c[u]++,c[v]++,c[lca]-=2;
	}
	get_ans(1);
	fout<<ans<<"\n";
	for(int i=1;i<=m;i++)
	{
		if(val[i]) fout<<E[i].u<<" "<<E[i].v<<"\n";
		else fout<<E[i].v<<" "<<E[i].u<<"\n";
	}
	return 0;
}
/*
构造题
先看出来最大值可以取满
然后构造可以使用欧拉回路,这题太妙了 
*/

CF1264E Beautiful League

原题及为竞赛图中已经给定一些边,求为其余边定向后,图中三元环个数最大值。

首先竞赛图三元环个数可以使用容斥计算,三元环个数=\(\binom{n}{3}-\sum_{i=1}^n\binom{d_i}{2}\),其中 \(d_i\)\(i\) 点的出度。具体计算方法是使用容斥,发现任意选的三个点中,不符合条件的只有一个点连向其余两个点,即有一个点出度为二。由于总点数一定,然后就可以使用网络流跑匹配求 \(\sum_{i=1}^n\binom{d_i}{2}\) 的最小值即可。代码用的是动态开点网络流,实际上可以不用,当时傻了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=60,inf=1e18,M=100010;
int ans[N][N],ansf,ansc,top[N],nw[N],nxt[M],cnt,S,T,vis[N][N],www[N][N],h[M],cur[M],totedge=1,fl[M],dis[M],n,m;
struct edge
{
	int to,nxt,w,cost,op;
} e[M<<1];
void add(int u,int v,int w,int cost,int op)
{
	totedge++;
	e[totedge].to=v;e[totedge].w=w;e[totedge].op=op;
	e[totedge].cost=cost;e[totedge].nxt=h[u];
	h[u]=totedge;
}
void add_edge(int u,int v,int w,int cost,int op)
{
	add(u,v,w,cost,op);add(v,u,0,-cost,op);
}
bool bfs()
{
	queue<int> q;
	for(int i=1;i<=cnt;i++) cur[i]=h[i],dis[i]=inf;
	dis[S]=0;q.push(S);fl[S]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();fl[u]=0;
		for(int i=h[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(e[i].w && dis[v]>dis[u]+e[i].cost)
			{
				dis[v]=dis[u]+e[i].cost;
				if(!fl[v]) fl[v]=1,q.push(v);
			}
		}
	}
	return dis[T]!=inf;
}
int dfs(int u,int val)
{
	if(u==T) return val;
	int w=0;fl[u]=1;
	for(int& i=cur[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(e[i].w && dis[v]==dis[u]+e[i].cost && !fl[v])
		{
			int x=dfs(v,min(e[i].w,val-w));
			if(x>0)
			{
				nxt[u]=v;
				ansc+=x*e[i].cost,w+=x;
				e[i].w-=x;e[i^1].w+=x;
			}
			if(w==val)
			{
				fl[u]=0;
				return w;
			}
		}
	}
	fl[u]=0;
	return w;
}
void Dinic()
{
	while(bfs())
	{
		ansf+=dfs(S,inf);
		for(int i=1;i<=n;i++)
		{
			if(nxt[nw[i]])
			{
				cnt++;top[i]++;nw[i]=cnt;
				add_edge(cnt,T,1,top[i]-1,0);
				for(int j=1;j<i;j++)
				{
					if(vis[j][i]!=2) add_edge(www[j][i],cnt,1,0,1);
				}
				for(int j=i+1;j<=n;j++)
				{
					if(vis[i][j]!=1) add_edge(www[i][j],cnt,1,0,2);
				}
			}
		}
	}
}
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	fin>>n>>m;
	cnt=2;S=1,T=2;
	for(int i=1;i<=m;i++)
	{
		int u,v;fin>>u>>v;
		if(u>v) vis[v][u]=2;
		else vis[u][v]=1;
	}
	for(int i=1;i<=n;i++)
	{
		top[i]=1;nw[i]=++cnt;
		add_edge(cnt,T,1,0,0);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			cnt++;www[i][j]=cnt;
			add_edge(S,cnt,1,0,0);
			if(!vis[i][j])
			{
				add_edge(cnt,i+2,1,0,2);
				add_edge(cnt,j+2,1,0,1);
			}
			else if(vis[i][j]==1) add_edge(cnt,j+2,1,0,1);
			else add_edge(cnt,i+2,1,0,2);
		}
	}
	Dinic();
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			int idx=www[i][j];
			for(int k=h[idx];k;k=e[k].nxt)
			{
				int v=e[k].to;
				if(v==S) continue;
				if(e[k^1].w==1)
				{
					if(e[k^1].op==1) ans[i][j]=1;
					else ans[j][i]=1;
				}
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			fout<<ans[i][j];
		}
		fout<<"\n";
	}
//	fout<<(n*(n-1)*(n-2)/6-ansc)*3<<"\n";
	return 0;
}
posted @ 2026-04-30 17:22  stuart220_chen  阅读(6)  评论(0)    收藏  举报