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

CF1264F Beautiful Fibonacci Problem

先别问为什么图论的第一个题是数论。我也想知道。

首先看到这种斐波那契数列+等差数列状物,最先想到的应该是斐波那契数列的一个恒等式,就是 \(F_{n+m}=F_{n}F_{m+1}+F_{n-1}F_{m}\) ,这样我们就可以愉快地拆那个等差了。

然后我们发现它只需要后 \(18\) 位 ,所以我们的一切运算都是在模 \(10^{18}\) 的意义下进行。而关于斐波那契数列模 \(10^k\) 有一个很经典的性质是最小循环节是 \(k=1\) 时为 \(60\)\(k=2\) 时为 \(300\)\(k\ge3\) 时为 \(1.5\times 10^{k}\) 。也就是说在 \(k\ge 3\) 的情况下一定有 \(F_{1.5\times 10^k\cdot i}\equiv 0\space (mod\space 10^k)\) ,然后我们令 \(N=12\times 10^k\) (这里为什么是 \(12\times 10^k\) 而不是 \(1.5\times 10^k\) 一是为了满足 \(k\) 较小的情况,第二条下面会有解释)。然后我们就会有如下几条引理。

  1. 对任意正整数 \(i\) 都有 \(F_{Ni}\equiv 0 \space (mod\space 10^k)\)

    证明:由于 \(N\) 一定是 \(F\) 的一个循环节,所以 \(F_{Ni}\equiv F_0\equiv 0\space (mod\space 10^k)\)

  2. 对任意正整数 \(x\) 都有 \(F_{Nx+1}\equiv {F_{N+1}}^x (mod\space 10^{2k})\)

    证明:数学归纳法,首先对于 \(x=1\) 时显然成立。

    现在假设当任意 \(x<k\) 成立时,对于 \(k\) 也成立。则有

    \(F_{kN+1}=F_{(k-1)N+1}F_{N+1}+F_{(k-1)N}F_{N}\equiv {F_{N+1}}^{k-1}F_{N+1}={F_{N+1}}^k\space (mod\space 10^{2k})\) 。成立,证毕。

  3. 然后我们考察 \(F_{N+1}\) 的性质,就有对任意 \(F_{N+1}\) 都可以写成形如 \(8\times 10^k\times t+1\) 的形式,其中需要满足 \(t\)\(10\) 互质,要证明这个,其实就是证明 \(F_{N+1}\equiv F_1\space (mod\space 8\times 10^k)\)

    证明:这个证明要用到 \(pisano\) 周期 ,我们可以知道 \(12\times 10^k\) 一定是一个斐波那契数列模 \(8\times 10^k\) 的循环节,这也是 \(N\)\(12\times 10^k\) 而非 \(1.5\times 10^k\) 的一个重大原因。

  4. \(F_{xN+1}\equiv 8\times 10^k\times t \times x+1\space (mod \space 10^{2k})\)

    证明:由于有 \(F_{xN+1}\equiv{F_{N+1}}^x\equiv (8\times 10^k\times t+1)^x\space (mod\space 10^{2k})\)

    所以我们将最右边的式子用二项式定理展开,由于第一项超过一次以后模 \(10^{2k}\) 就恒为 \(0\) ,所以我们忽略掉这些值。那么就只会剩余一次项和常数项。即为 \(8\times 10^k\times t \times x+1\) ,得证。

证明完这些引理,我们就可以开始构造了,我们需要对于每一个 \(v=a+id,i\in [0,n)\cap\mathbb{Z}\) 找到一个 \(p\) 满足 \(F_p\equiv v\times 10^u+q\space (mod\space 10^{18})\) ,其中 \(u\le 12,q\le 10^u\) ,这样显然就做完了,因为我们可以取最高的几位得到 \(v\) 。由于运算是在模 \(10^{18}\) 的意义下进行,所以可以让 \(k=9\) ,我们设 \(t\) 在模 \(10^9\) 意义下的逆元为 \(t^{-1}\)

\(X=(125\times t^{-1}\times a)\%10^k,Y=X=(125\times t^{-1}\times d)\%10^k\) 那我们考虑构造出一组解为 \(b=XN+1,e=YN\) 。下证明这组解合法。

\[\begin{align*} F_{b+ie}&=F_{(X+Yi)N+1}\\ &\equiv 8\times 10^9\times t\times (X+Yi)+1\\ &\equiv8\times 10^9\times t \times125\times t^{-1}\times (a+id)+1\\ &=10^{12}(10^9m+1)(a+id)+1\\ &\equiv 10^{12}(a+id)+1\space (mod\space 10^{18}) \end{align*} \]

得证。

所以具体构造就是设 \(m=368131125\)\(125\times t^{-1}\) 的具体值),\(mod=10^9\)\(N=1.2\times 10^{10}\) 。然后让 \(b=((m\times a)\%mod)\times N+1,e=(m\times d\%mod)\times N\) 即可。代码很短,只有输入和输出。

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int unsigned 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 m=368131125,mod=1e9,N=1.2e10;
signed main()
{
	int n,a,d;fin>>n>>a>>d;
	fout<<(m*a%mod)*N+1<<" "<<(m*d%mod)*N<<"\n";
	return 0;
}

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;
}
/*
构造题
先看出来最大值可以取满
然后构造可以使用欧拉回路,这题太妙了 
*/

AT_agc033_f [AGC033F] Adding Edges

图上构造好题。

我们不妨设原图为 \(G_0\) ,最后生成的图为 \(G\)

我们先考虑 \(a,b,c\) 在树上就按照这个顺序出现。那么一条边 \((u,v)\in G\) ,当且仅当存在 \(u=v_1,v_2,v_3\dots v_{k-1},v_k=v\) 使得对于每一个 \(i\in[1,k-1]\) ,都满足 \((v_i,v_{i+1})\in G_0\)\(v_1,v_2,\dots v_k\) 在树上的某一条链上顺序出现。

我们现在考虑将原图改成这个形式,对于树上某条简单路径上顺序出现的三个点 \(a,b,c\) ,若 \((a,b),(a,c)\in G_0\) 则我们可以将边 \((a,c)\) 替换为 \((b,c)\) 使答案不变。

现在考虑快速处理这个过程。考虑依次加入 \(G_0\) 中的边,并记 \(f(x,y)\) 为树的根为 \(x\) 时当前生成的图 \(G\) 中与 \(y\) 有连边的与 \(x\) 在该树上的祖先。则加入边 \((x,y)\) 时如下更新 \(f(x,y)\)

  1. \(f(x,y)=y\) 时,显然不需要加入这条边。
  2. \(f(x,y)\) 有值且不为 \(y\) 时,我们需要将 \((f(x,y),y)\) 加入待加入边的队列。
  3. \(f(x,y)\) 没有值的时候,我们先加入边 \((x,y)\) 和边 \((y,x)\) (及更新 \(f(x,y)\)\(f(y,x)\) 的值),然后更新以 \(x\) 为根, \(y\) 子树内的 \(f(x,u)\)\(y\)

具体更新方法是如果 \(f(x,u)\) 有值,那么将 \((y,u)\) 加入待加入边的队列。否则将 \(f(x,u)\) 更新为 \(y\) 。注意反过来对于 \(y,x\) 也要跑一遍相同的算法,因为我们需要的是无向图。

最后统计答案时就是分别以每个节点为树的根跑 \(dfs\) 统计答案,注意最后答案要除以二,因为我们会正反统计两边。

代码:

#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 f[N][N],p[N][N],ans;
vector<int> g[N];
il void get_fa(int u,int* fa)
{
	for(int v:g[u])
	{
		if(v==fa[u]) continue;
		fa[v]=u;
		get_fa(v,fa);
	}
}
queue<int> qx,qy;
il void dfs(int u,int rt,int t)
{
	if(u!=t && p[rt][u])
	{
		qx.push(u),qy.push(t);
		return;
	}
	p[rt][u]=t;
	for(int v:g[u])
	{
		if(v==f[rt][u]) continue;
		dfs(v,rt,t);
	}
}
il void add(int u,int v)
{
	qx.push(u),qy.push(v);
	while(!qx.empty())
	{
		int x=qx.front(),y=qy.front();
		qx.pop(),qy.pop();
		if(p[x][y]==y) continue;
		if(p[x][y])
		{
			qx.push(p[x][y]),qy.push(y);
			continue;
		}
		if(p[y][x])
		{
			qx.push(p[y][x]),qy.push(x);
			continue;
		}
		p[x][y]=y,p[y][x]=x;
		dfs(y,x,y);dfs(x,y,x);
	}
}
il void get_ans(int u,int rt,int t)
{
	if(t!=u && p[t][u])
	{
		ans++;t=u;
	}
	for(int v:g[u])
	{
		if(v==f[rt][u]) continue;
		get_ans(v,rt,t);
	}
}
signed main()
{
	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<=n;i++) get_fa(i,f[i]);
	for(int i=1;i<=m;i++)
	{
		int u,v;fin>>u>>v;add(u,v);
	}
	for(int i=1;i<=n;i++) get_ans(i,i,i);
	fout<<ans/2;
	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;
}

CF1338E JYPnation

首先我们先考虑 \(dis(A,B)\) 的值为 \(614n\) 的情况,我们找出原图的所有 SCC ,那么所有在链上更靠后的 SCC 里面的点到前面的点的距离都是 \(614n\) 而所有在链上更靠前的 SCC 里面的点到后面的点的距离都是 \(1\) ,实际上这里不需要跑 tarjan ,因为如果竞赛图的 SCC 大小大于等于三,那么其中就一定会出现三元环,如果前面的 SCC 的大小超过了二,那么就出现了题目中的图案,不符合题目要求了,所以实际上前面的 SCC 大小都只能为一,只有最后一个需要考虑 SCC 内的情况。

引理 答案不超过 \(3\)

证明 考虑一条长度为 \(4\) 的最短路 \(a\rightarrow b\rightarrow c\rightarrow d\rightarrow e\) 。由于不能继续更新答案,所以这五个点中的其他连边方向就确定了。

此时 \(a,c,d,e\) 构成了题目中不可以出现的子图,所以不合法。

所以我们只需要求出最短路长度为 \(1,2\) 的个数就好。长度为 \(3\) 的只需要那总的点对数减去长度为 \(1,2\) 的点对数即可。长度为 \(1\) 的就是路径条数。下面考虑长度为 \(2\) 的个数。

\(u\)\(v\) 的距离为 \(2\) 的条件是:存在 \(v\rightarrow u\) ,且存在点 \(a\) 使得有边 \(u\rightarrow a,a\rightarrow v\) 就是三元环状物,但是直接枚举太慢了,所以我们考虑一些更优秀的做法。

我们考虑 \(u\) 的两个出点 \(a\)\(b\) ,不妨设 \(a,b\) 之间的连边为 \(a\rightarrow b\) ,那么由于不能出现三元环连向同一个点( \(u,v,a\) 连向 \(b\) ),所以 \(b,v\) 之间的连边必定为 \(b\rightarrow v\) ,所以我们只需要统计 \(u\) 的所有出点的导出子图中拓扑序最靠后的那个点,但这里还遗留了一个问题,就是在同一个 SCC 一个点的所有出点的导出子图一定是一个 DAG。我们考虑证明:

我们假设 \(u\) 的所有出点的导出子图不为 DAG 则一定存在一个三元环 \(x,y,z\) 这其中一旦有任意一个点连向一点 \(v\) ,则另外两点一定也连向 \(v\) ,此时 \(x,y,z,v\) 构成题目中不能出现的样子,所以 \(x,y,z\) 不能连向其余任意一点,与其余点都不在一个 SCC 里面,与 \(u,x,y,z\) 在同一个 SCC 中矛盾,所以 \(u\) 的所有出点的导出子图一定是 DAG ,证毕。

所以我们只需要统计每个点 \(u\) 的出点中拓扑序最靠后的点 \(val_u\) ,然后枚举每条边 \(u\rightarrow v\) ,判断 \(val_v\) 是否连向 \(u\) 即可。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ll 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=8010;
bool vis[N][N];
int chu[N],id[N],mn[N],fl[N];
bool cmp(int x,int y)
{
	return chu[x]>chu[y];
}
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int n;fin>>n;
	for(int i=1;i<=n;i++)
	{
		string s;fin>>s;s=" "+s;
		for(int j=1;j<=n/4;j++)
		{
			int x=0;
			if(s[j]>='0' && s[j]<='9') x=s[j]-'0';
			else x=s[j]-'A'+10;
			for(int k=j*4;k>=j*4-3;k--)
			{
				vis[i][k]=x&1;
				if(vis[i][k]) chu[i]++;
				x>>=1;
			}
		}
		id[i]=i;
	}
	ll ans=0,cnt=0;
	sort(id+1,id+1+n,cmp);
	for(int i=1;i<=n;i++)
	{
		if(chu[id[i]]==n-i) fl[id[i]]=1,ans+=1ll*(n-i)*614*n+n-i;
		else
		{
			cnt=(n-i+1)*(n-i);
			break;
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(fl[i]) continue;
		for(int j=1;j<=n;j++)
		{
			if(fl[j] || !vis[i][j]) continue;
			ans++;cnt--;
			if(!mn[i]) mn[i]=j;
			else if(vis[mn[i]][j]) mn[i]=j;
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(fl[i]) continue;
		for(int j=1;j<=n;j++)
		{
			if(fl[j] || !vis[i][j]) continue;
			if(vis[mn[j]][i])  ans+=2,cnt--;
		}
	}
	ans+=cnt*3;
	fout<<ans;
	return 0;
}

【UNR #5】获奖名单

要求组成回文串,我们可以考虑将其从中间划分开然后将后半部分翻转过来与前半部分做匹配类似于下面这样:

我们发现有三种情况,一种是相同的两个,一种是先是一个,然后是两个两个错开,最后一个结尾,最后一种是为偶数时中间连个可以有两个相同的组成。其中第一种和第三种都可以直接最后再进行判断,较难处理的是第二种。

具体处理方法是把两个的数对 \((a,b)\) 当成 \(a\)\(b\) 连一条无向边,单个的 \(x\) 可以看成源点 \(0\)\(x\) 连边,然后所有的第二种类型就可以从源点开始跑欧拉回路(欧拉路径),是欧拉路径时就是奇数情形,最后一个位置不回到源点。然后剩下的部分就是一些关于第一三种的特判。具体实现时可以开两个vector,一个存前半部分,一个存反着的后半部分。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ll long long
bool St;
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=500010;
vector<int> b[N];
int d[N],vis[N],tp,cur[N],fl[N];
pair<int,int> st[N];
vector<pair<int,int> > ans,ans2;
map<pair<int,int>,vector<pair<int,int> > > mp;
struct node
{
	int v,idx,op;
};
struct edge
{
	int u,v;
} e[N];
vector<node> g[N];
bool Ed;
void get_oula(int u)
{
	for(int i=cur[u]++;i<(int)g[u].size();i=cur[u]++)
	{
		int v=g[u][i].v;
		int iii=g[u][i].idx;
		if(fl[iii]) continue;
		fl[iii]=1;
		get_oula(v);
		st[++tp]={iii,g[u][i].op};
	}
}
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	cerr<<(&St-&Ed)/1024.0/1024.0<<"\n";
	int n,m;fin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int op;fin>>op;
		if(op==1)
		{
			int x;fin>>x;
			g[m+1].push_back({x,i,0});
			g[x].push_back({m+1,i,0});
		}
		else
		{
			int u,v;fin>>u>>v;
			e[i]={u,v};
			g[u].push_back({v,i,0});
			g[v].push_back({u,i,1});
		}
	}
	get_oula(m+1);
	for(int i=1;i<=tp;i++) vis[st[i].first]=1;
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])
		{
			int u=e[i].u,v=e[i].v;
			if(u<v) mp[{u,v}].push_back({i,0});
			else mp[{v,u}].push_back({i,1});
		}
	}
	int fl=0,idx=0;
	for(auto i:mp)
	{
		vector<pair<int,int> > tmp=i.second;
		if(tmp.size()&1)
		{
			fl=1,idx=tmp[0].first;
			for(int j=1;j<(int)tmp.size();j+=2)
			{
				ans.push_back({tmp[j].first,tmp[j].second});
				ans2.push_back({tmp[j+1].first,tmp[j+1].second});
			}
		}
		else
		{
			for(int j=0;j<(int)tmp.size();j+=2)
			{
				ans.push_back({tmp[j].first,tmp[j].second});
				ans2.push_back({tmp[j+1].first,tmp[j+1].second});
			}
		}
	}
	for(int i=tp;i>=1;i--)
	{
		if(i&1) ans.push_back(st[i]);
		else ans2.push_back(st[i]);
	}
	if(fl) ans.push_back({idx,0});
	reverse(ans2.begin(),ans2.end());
	for(auto i:ans) fout<<i.first<<" ";
	for(auto i:ans2) fout<<i.first<<" ";
	fout<<"\n";
	for(auto i:ans) fout<<i.second<<" ";
	for(auto i:ans2) fout<<(i.second^1)<<" ";
	return 0;
}

【UER #9】知识网络

首先我们考虑类似圆方树的建图方式,建 \(k\) 个虚点表示一种标签类型,然后每个虚点向同类型的点连一条边权为 \(1\) 的有向边,反着连一条边权为 \(0\) 的有向边。然后题目所给的网络就是正常连无向边。此时这张图里两个点 \(u,v\) 的最短路就是题目中所描述的 \(f(u,v)\)

然后我们可以对于每一种类型的点计算答案,考虑从这种类型的虚点为起点跑最短路(可以用 01bfs ),然后再建出来最短路 DAG ,这样如果某一个这个类型的点 \(u\) 在最短路 DAG 上可以到达另一个点 \(v\) ,则 \(f(u,v)==dis_v\) ,否则 \(u\) 到点 \(v\) 的最短路就必须经过点 \(u\) 类型的虚点,所以 \(f(u,v)=dis_v+1\) 。用 \(bitset\) 维护 DAG 可达性即可。具体实现时可以换成每 \(64\) 个用一个 ull 存,常数会小一点,注意一下数一个 ull 的二进制位下一的个数要用 \(\_\_builtin\_popcountll()\) 不要因为这个调半天。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ll long long
#define ull unsigned long long
bool St;
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=60010,inf=1e9;
int c[N],vis[N],dis[N],idx[N],d[N],cnt[N];
ll ans[N];ull st[N];
vector<pair<int,int> > g[N];
vector<int> dag[N];
bool Ed;
signed main()
{
//	freopen("1.in","r",stdin);
//	freopen(".out","w",stdout);
	cerr<<(&St-&Ed)/1024.0/1024.0<<"\n";
	int n,m,k;fin>>n>>m>>k;
	for(int i=1;i<=n;i++)
	{
		fin>>c[i];
		g[c[i]+n].push_back({i,1});
		g[i].push_back({c[i]+n,0});
	}
	for(int i=1;i<=m;i++)
	{
		int u,v;fin>>u>>v;
		g[u].push_back({v,1});
		g[v].push_back({u,1});
	}
	for(int i=n+1;i<=n+k;i++)
	{
		for(int j=1;j<=n+k;j++) dis[j]=inf,vis[j]=cnt[j]=0;
		deque<int> q;
		q.push_back(i);dis[i]=0;
		while(!q.empty())
		{
			int u=q.front();
			q.pop_front();
			if(vis[u]) continue;
			vis[u]=1;
			for(auto j:g[u])
			{
				int v=j.first;
				if(j.second)
				{
					if(dis[u]+1<dis[v])
					{
						dis[v]=min(dis[v],dis[u]+1);
						q.push_back(v);
					}
				}
				else
				{
					if(dis[u]<dis[v])
					{
						dis[v]=min(dis[v],dis[u]);
						q.push_front(v);
					}
				}
			}
		}
		int tot=0;
		for(auto j:g[i]) idx[j.first]=++tot;
		for(int j=1;j<=n+k;j++) vector<int>().swap(dag[j]);
		for(int j=1;j<=n+k;j++)
		{
			if(!vis[j]) continue;
			for(auto f:g[j])
			{
				int v=f.first;
				if(dis[v]==dis[j]+f.second) dag[j].push_back(v);
			}
		}
		for(int l=1;l<=tot;l+=64)
		{
			for(int j=1;j<=n+k;j++)
			{
				st[j]=0;
				for(int v:dag[j]) d[v]++;
			}
			queue<int> Q;Q.push(i);
			while(!Q.empty())
			{
				int u=Q.front();Q.pop();
				if(idx[u]>=l && idx[u]<=l+63) st[u]|=1ull<<(idx[u]-l);
				for(int v:dag[u])
				{
					st[v]|=st[u];
					d[v]--;
					if(!d[v]) Q.push(v);
				}
			}
			for(int j=1;j<=n;j++) cnt[j]+=__builtin_popcountll(st[j]);
		}
		for(int j=1;j<=n;j++)
		{
			if(!vis[j]) continue;
			if(!idx[j])
			{
				ans[dis[j]]+=cnt[j];
				ans[dis[j]+1]+=tot-cnt[j];
			}
			else ans[2]+=tot-1;
		}
		for(auto j:g[i]) idx[j.first]=0;
	}
	ll sum=1ll*n*(n-1);
	for(int i=1;i<=2*k;i++) fout<<ans[i]/2<<" ",sum-=ans[i];
	fout<<sum/2;
	return 0;
}
posted @ 2026-04-30 17:22  stuart220_chen  阅读(10)  评论(0)    收藏  举报