20200710 图论(睿智题

AT2668 [AGC017E] Jigsaw

把积木看做点,能否互补看做边,边数过多。

经过转化,把左接地和右接地分别用正负表示,这样互补就变成了在同一个点,于是积木可以看做一条左边向右边对应点的连边,一条正点开始、负点结束的路径就对应了左接地,中间互补,右接地的摆放方案。

于是现在相当于是问能否把所有的边拆成若干条正点开始,负点结束的路径。

首先有两个必要条件:正点的出度>入度,负点的入度>出度。
我们新建一个超级源点,源点向正点连 出度-入度 条边,负点向源点连 入度-出度条边。
那么一条合法路径就是一条经过源点的回路。
于是问题转化为判断这个图是否有欧拉回路。显然每个点都满足出度=入度,那么现在就是要判断图是否连通,即原图的每个弱联通分量都要有至少一个点满足原出度不等于原入度。

Code:

#include<bits/stdc++.h>
#define maxn 405
using namespace std;
int n,h,in[maxn],out[maxn],f[maxn];
bool ok[maxn];
int find(int x){return !f[x]?x:f[x]=find(f[x]);}
int main()
{
	scanf("%d%d",&n,&h);
	for(int i=1,A,B,C,D;i<=n;i++){
		scanf("%d%d%d%d",&A,&B,&C,&D);
		int x=C?C+h:A, y=D?D:B+h;
		out[x]++,in[y]++;
		if((x=find(x))!=(y=find(y))) f[x]=y;
	}
	for(int i=1;i<=h;i++) if(in[i]>out[i]) return puts("NO"),0;
	for(int i=h+1;i<=h<<1;i++) if(out[i]>in[i]) return puts("NO"),0;
	for(int i=1;i<=h<<1;i++) if(in[i]!=out[i]||!(in[i]+out[i])) ok[find(i)]=1;
	for(int i=1;i<=h<<1;i++) if(!f[i]&&!ok[i]) return puts("NO"),0;
	puts("YES");
}

LOJ#2737. 「JOISC 2016 Day 3」电报

在这里插入图片描述
出度为1,强连通就是连成一个大环。

再想一想就是保留原图的若干条链,使其价值和最大。
那么一个点所有指向它的点中只能保留一个,其它都要断掉。在这其中保留最大值。
这样处理之后可能仍然会留下若干环,一个环需要选择一个点,指向它的点中保留的点从环上点换成非环点。
存每个点指向它的点中 c i c_i ci 的最大值 m x mx mx,以及指向它的非环点中 c i c_i ci 的最大值 l m x lmx lmx 即可。

Code:

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
int n,a[maxn],c[maxn],vis[maxn],mx[maxn],lmx[maxn];//lmx: mx on chain.
long long ans;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&c[i]),ans+=c[i];
	for(int i=1;i<=n;i++) if(!vis[i]){
		int x=i;
		for(;!vis[x];x=a[x]) vis[x]=i;
		if(vis[x]==i) {int t=0; for(;~vis[x];x=a[x]) vis[x]=-1,t++; if(t==n) return puts("0"),0;}
	}
	for(int i=1;i<=n;i++){
		mx[a[i]]=max(mx[a[i]],c[i]);
		if(~vis[i]) lmx[a[i]]=max(lmx[a[i]],c[i]);
	}
	for(int i=1;i<=n;i++) ans-=mx[i];
	for(int i=1;i<=n;i++) if(vis[i]==-1){
		int mn=2e9;
		for(int x=i;vis[x];x=a[x]) mn=min(mn,mx[x]-lmx[x]),vis[x]=0;
		ans+=mn;
	}
	printf("%lld\n",ans);
}

AT2046 [AGC004F] Namori

editorial1
editorial2

神仙转化。
在这里插入图片描述
官方题解给出的证明:
在这里插入图片描述
第一段说明了任何时刻一个棋子从起点到终点的路径都是可行的。把树的情况边上的权值看做一些箭头,那么它一定连成了从所有起点到所有终点的路径。走过这些路径即可说明结论的充分性。

偶环定前缀和中点。
奇环删掉一部分。

AT2006 [AGC003F] Fraction of Fractal

k k k级分形是在 k − 1 k-1 k1 级分形的基础上,把每个黑点替换为给出的 n ∗ m n*m nm的母图生成。
等价的生成方式是在 n ∗ m n*m nm的母图的基础上,把每个黑点替换为 k − 1 k-1 k1 级分形。

记母图的黑点数为 c c c,左右相邻的黑点对数为 a a a,左右边界相邻的黑点对数为 b b b

由第一种生成方式可以得到: k k k 级分形的连通块数 = k − 1 k-1 k1 级分形的黑点个数 - 左右相邻的黑点对数。
由第二种生成方式可以得到:
k k k 级分形左右相邻的黑点对数 = k − 1 k-1 k1 级分形的左右相邻的黑点对数 ∗ c *c c + 左右边界相邻的黑点对数*母图中左右相邻的黑点对数。
k k k 级分形左右边界相邻的黑点对数 = k − 1 k-1 k1 级分形左右边界相邻的黑点对数 * 母图中左右边界相邻的黑点对数。

然后就可以写矩乘了,本来是要 3*3 的,但是莫名其妙地大家都写的是 2*2 …左乘 [ c    a 0    b ] \begin{bmatrix}c~~a\\0~~b\end{bmatrix} [c  a0  b]

图形生成:

#include<bits/stdc++.h>
using namespace std;
char a[1005][1005],b[6][1005][1005];
int n,m,N,M;
int main()
{
	freopen("1.in","r",stdin);
	freopen("1.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%s",a[i]+1);
	N=M=1; b[0][1][1]='#';
	for(int k=1;k<=4;k++){
		for(int i=0;i<n;i++)
			for(int j=0;j<m;j++)
				if(a[i+1][j+1]=='#'){
					for(int x=1;x<=N;x++)
						for(int y=1;y<=M;y++)
							b[k][i*N+x][j*M+y]=b[k-1][x][y];
				}
				else for(int x=1;x<=N;x++)
						for(int y=1;y<=M;y++)
							b[k][i*N+x][j*M+y]='.';
		N*=n,M*=m;
		for(int i=1;i<=N;i++) puts(b[k][i]+1);
	}
}

Code:

#include<bits/stdc++.h>
#define maxn 1005
using namespace std;
const int mod = 1e9+7;
int n,m,a[maxn][maxn],cnt,UD,LR,clr,cud;
long long k;
char s[maxn];
int Pow(int a,long long b){int s=1;for(;b;b>>=1,a=1ll*a*a%mod) b&1&&(s=1ll*s*a%mod);return s;}
struct Mat{
	int s[2][2];
	Mat(){memset(s,0,sizeof s);}
	Mat operator * (const Mat &B)const{
		Mat ret;
		for(int k=0;k<2;k++)
			for(int i=0;i<2;i++) if(s[i][k])
				for(int j=0;j<2;j++) if(B.s[k][j])
					ret.s[i][j]=(ret.s[i][j]+1ll*s[i][k]*B.s[k][j])%mod;
		return ret;
	}
}f,g;
void solve(int a,int b){
	g.s[0][0]=cnt,g.s[0][1]=a,g.s[1][1]=b,f=g;
	for(k-=2;k;k>>=1,g=g*g) if(k&1) f=f*g;
}
int main()
{
	scanf("%d%d%lld",&n,&m,&k);
	if(k<=1) return puts("1"),0;
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		for(int j=1;j<=m;j++) a[i][j]=s[j]=='#',cnt+=a[i][j],clr+=a[i][j]&a[i][j-1],cud+=a[i][j]&a[i-1][j];
	}
	for(int i=1;i<=n;i++) LR+=a[i][1]&a[i][m];
	for(int i=1;i<=m;i++) UD+=a[1][i]&a[n][i];
	if(LR&&UD) return puts("1"),0;
	if(!LR&&!UD) return printf("%d\n",Pow(cnt,k-1)),0;
	LR?solve(clr,LR):solve(cud,UD);
	printf("%d\n",(f.s[0][0]-f.s[0][1]+mod)%mod);
}

CF1240F Football

找题人的题解
在这里插入图片描述
x x x 开始走 A B AB AB 交错的路,中间经过的点交换之后没变化。如果找到某个点没有 A A A B B B 的出边,替换之后新增的颜色为1,原来的颜色减少1,仍然合法。如果找回了 x x x,直接替换可行。找到了 y y y,可行。所以一定可以找到。
在这里插入图片描述
Code by Scut82

二分图方法:
x x x 拆成两个点,分别连边,保证各自的最大值减最小值<=1,那么合起来就<=2
形成二分图之后就可以方便地找交错路了,找到 x x x 没有的颜色 A A A,找到 y + n y+n y+n 没有的颜色 B B B,然后从 y + n y+n y+n 走把 A A A 替换成 B B B,可以保证不会走回 x x x
如果 k k k 条边都连满了就新开一个点。
在这里插入图片描述
Code by Freopen:

#include<bits/stdc++.h>
#define maxn 1005
using namespace std;
 
int n,m,K,w;
int nxt[5005][maxn],deg[maxn],ed[maxn][maxn],col[maxn],id[maxn];
 
void dfs(int id,int a,int b){
	swap(ed[id][b],ed[id][a]);
	swap(nxt[id][b],nxt[id][a]);
	col[ed[id][b]]=b;
	if(nxt[id][b]) 
		dfs(nxt[id][b],b,a);
}
 
int main(){
	scanf("%d %d %d",&n,&m,&K);
	int x,y,p1,p2,T=0;
	for(int i=1;i<=n;i++) scanf("%d",&x);
	for(int i=1;i<=m;i++){
		scanf("%d %d",&x,&y);y+=n;p1=p2=0;
		if(deg[x]%K==0) id[x]=++T;deg[x]++;x=id[x];
		if(deg[y]%K==0) id[y]=++T;deg[y]++;y=id[y];
		for(int j=1;j<=K;j++) if(!nxt[x][j] && !nxt[y][j]) p1=j,j=K+1;
		if(!p1){
			for(int j=1;j<=K;j++) if(!nxt[x][j]) p1=j,j=K+1;
			for(int j=1;j<=K;j++) if(!nxt[y][j]) p2=j,j=K+1;
			dfs(y,p1,p2);
		}
		nxt[x][p1]=y,nxt[y][p1]=x,ed[x][p1]=i,ed[y][p1]=i,col[i]=p1;
	}
	for(int i=1;i<=m;i++) printf("%d\n",col[i]);
}

官方题解的方法:
https://www.cnblogs.com/Purple-wzy/p/13279053.html

LOJ#546. 「LibreOJ β Round #7」网格图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值