UOJ Round #6 题解

A

显然可以找到这样一条关系:
h i + 1 ≡ ( h i − t i × 2 6 n − 1 ) × 26 + t i ( m o d p ) h_{i+1} \equiv (h_i-t_i\times 26^{n-1})\times 26+t_i \pmod p hi+1(hiti×26n1)×26+ti(modp)

移项得到:
t i ≡ h i + 1 − 26 h i 1 − 2 6 n ( m o d p ) t_i \equiv \frac {h_{i+1}-26h_i} {1-26^{n}} \pmod p ti126nhi+126hi(modp)

于是我们就可以用 h h h 倒推出 t t t 了。

但是有个问题,万一 1 − 2 6 n ≡ 0 ( m o d p ) 1-26^n\equiv 0 \pmod p 126n0(modp) 怎么办?

这样的话我们不能将它作为除数,先移回左边,柿子变成:
h i + 1 ≡ 26 h i ( m o d p ) h_{i+1}\equiv 26h_i \pmod p hi+126hi(modp)

柿子变成了一个与 t t t 无关的东西,也就是说,要求的字符串只需要满足 f ( a 0 ) = h 0 f(a_0)=h_0 f(a0)=h0 即可,那么我们将 h 0 h_0 h0 26 26 26 进制下分解一波即可。

代码如下:

#include <cstdio>
#define maxn 100010

int n,mod,h[maxn];
int ksm(int x,int y)
{
    
    
	int re=1;
	while(y)
	{
    
    
		if(y&1)re=1ll*re*x%mod;
		x=1ll*x*x%mod;y>>=1;
	}
	return re;
}
#define inv(x) ksm(x,mod-2)

int main()
{
    
    
	scanf("%d %d",&n,&mod);
	int wulala=ksm(26,n);
	for(int i=1;i<=n;i++)
	scanf("%d",&h[i]);
	if(wulala!=1)
	{
    
    
		for(int i=1;i<n;i++)
		printf("%c",(1ll*h[i+1]-(26ll*h[i])%mod+mod)%mod*inv((1-wulala+mod)%mod)%mod+'a');
		printf("%c",(1ll*h[1]-(26ll*h[n])%mod+mod)%mod*inv((1-wulala+mod)%mod)%mod+'a');
	}
	else
	{
    
    
		for(int i=2;i<=n;i++)h[i]=0;
		int now=1;
		while(h[now]>=26)h[now+1]=h[now]/26,h[now++]%=26;
		for(int i=n;i>=1;i--)printf("%c",h[i]+'a');
	}
}

B

先构造 1000 1000 1000 12 12 12 个点的无向图,每条边生成概率为 0.8 0.8 0.8,求出他们的生成树数量。

假如两张图之间用桥连接(即只用一条边),那么得到的新图的生成树数量是原来两张图的生成树数量的乘积,所以对于每个询问,尝试拿出 4 4 4 张图拼在一起,看看生成树数量是否为 k k k 即可。

具体凑法:先将那 1000 1000 1000 张图两两拼在一起得到 1 0 6 10^6 106 张,然后用哈希或 m a p map map 将它们的生成树数量存起来,然后每次枚举一遍,每次看看是否存在生成树数量为 k除以当前图的生成树数量 的图即可。

这题是一定有解的,所以要是输出了 QwQ 那你就要 QAQ 了。

有解的具体证明啥的可以去看官方题解,这里就随便扯扯:你看一开始有 1000 1000 1000 张图,四次方就是 1 0 12 10^{12} 1012,模了 998244353 998244353 998244353 后可以看做他们在 [ 0 , 998244352 ] [0,998244352] [0,998244352] 中平均分布,那么每个位置甚至有几百个图,所以是不愁没解的。

代码如下(这个代码有点看脸,如果评测机比较慢可能会TLE qwq,但是AC几率还是很大的):

#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
#define maxn 20
#define maxm 1010
#define mod 998244353

const int n=12;
int T;
struct par{
    
    
	int x,y;
	par(int xx=0,int yy=0):x(xx),y(yy){
    
    }
};
struct gragh{
    
    
	int E,f[maxn][maxn],DET;
	par road[maxn*maxn];
	gragh():E(0){
    
    }
	void buildroad(int x,int y)
	{
    
    
		road[++E]=(par){
    
    x,y};
		f[x][y]--;f[y][x]--;
		f[x][x]++;f[y][y]++;
	}
	int det(int l)
	{
    
    
		int re=1,fu=1;
		for(int i=1;i<=l;i++)
		for(int j=1;j<=l;j++)
		f[i][j]=(1ll*f[i][j]+mod)%mod;
		for(int i=1;i<=l;i++)
		{
    
    
			for(int j=i+1;j<=l;j++)
			while(f[j][i])
			{
    
    
				int p=f[i][i]/f[j][i];
				for(int k=i;k<=l;k++)
				f[i][k]=(1ll*f[i][k]-1ll*p*f[j][k]%mod+mod)%mod;
				swap(f[i],f[j]); fu=-fu;
			}
			if(!f[i][i])return 0;
			re=1ll*re*f[i][i]%mod;
		}
		re=(1ll*re*fu+mod)%mod;
		return re;
	}
	void Rand()
	{
    
    
		for(int i=2;i<=n;i++)buildroad(rand()%(i-1)+1,i);
		for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
		if(f[i][j]==0&&rand()%10<8)buildroad(i,j);
		DET=det(n-1);
	}
	void print(int x)
	{
    
    
		for(int i=1;i<=E;i++)
		printf("%d %d\n",road[i].x+x,road[i].y+x);
	}
}G[maxm];
map<int,par> Map;
par S[maxm*maxm];
int s[maxm*maxm],inv_s[maxm*maxm];
int ksm(int x,int y)
{
    
    
	int re=1;
	while(y)
	{
    
    
		if(y&1)re=1ll*re*x%mod;
		x=1ll*x*x%mod;y>>=1;
	}
	return re;
}
#define inv(x) ksm(x,mod-2)
void work()
{
    
    
	for(int i=1;i<=maxm-10;i++)G[i].Rand();
	for(int i=1;i<=maxm-10;i++)
	for(int j=1;j<=maxm-10;j++)
	Map[(1ll*G[i].DET*G[j].DET)%mod]=S[(i-1)*1000+j]=par(i,j),
	s[(i-1)*1000+j]=(1ll*G[i].DET*G[j].DET)%mod;
	for(int i=1;i<=1000000;i++)
	inv_s[i]=inv(s[i]);
}
void output(int a,int b,int c,int d)
{
    
    
	printf("48 %d\n",G[a].E+G[b].E+G[c].E+G[d].E+3);
	printf("12 13\n24 25\n36 37\n");
	G[a].print(0);G[b].print(12);G[c].print(24);G[d].print(36);
}

int main()
{
    
    
	scanf("%d",&T);
	srand(1337+T); work();
	while(T--)
	{
    
    
		int x;
		scanf("%d",&x);
		if(!x){
    
    printf("2 0\n");continue;}
		for(int i=1;i<=1000000;i++)
		{
    
    
			int p=1ll*x*inv_s[i]%mod;
			if(Map.find(p)!=Map.end()){
    
    output(S[i].x,S[i].y,Map[p].x,Map[p].y);break;}
		}
	}
}

如果你想康康自己的代码构造的图是否正确,这里顺便给一个测试的代码 (用法?这就不用我教了吧qwq)

#include <cstdio>
#include <algorithm>
using namespace std;
#define mod 998244353

int n,m;
int f[110][110];
int det(int l)
{
    
    
	int re=1,fu=1;
	for(int i=1;i<=l;i++)
	for(int j=1;j<=l;j++)
	f[i][j]=(1ll*f[i][j]+mod)%mod;
	for(int i=1;i<=l;i++)
	{
    
    
		for(int j=i+1;j<=l;j++)
		while(f[j][i])
		{
    
    
			int p=f[i][i]/f[j][i];
			for(int k=i;k<=l;k++)
			f[i][k]=(1ll*f[i][k]-1ll*p*f[j][k]%mod+mod)%mod;
			swap(f[i],f[j]); fu=-fu;
		}
		if(!f[i][i])return 0;
		re=1ll*re*f[i][i]%mod;
	}
	re=(1ll*re*fu+mod)%mod;
	return re;
}

int main()
{
    
    
	scanf("%d %d",&n,&m);
	for(int i=1,x,y;i<=m;i++)
	scanf("%d %d",&x,&y),
	f[x][y]--,f[y][x]--,f[x][x]++,f[y][y]++;
	printf("%d",det(n-1));
}

C

wtcl,膜了好久题解才明白QAQ……

first

先看完全图的情况,此时可以发现一个规律,当有 k k k 只狗生病时,开枪时间一定是第 k k k 天,并且 k k k 只狗同时阵亡。

由于此时是完全图,所以我们可以认为这 k k k 只狗的主人的思维历程都是一样的,随便抓一个出来剖析一下内心,就发现他是这样想的:如果我的狗没生病,那么剩下 k − 1 k-1 k1 只懒狗的话,开枪时间就应该是第 G [ k − 1 ] G[k-1] G[k1] 天,如果那个时候没人开枪,那我的狗肯定病了,我要在第 G [ k − 1 ] + 1 G[k-1]+1 G[k1]+1 (即 G [ k ] G[k] G[k]) 天崩了它。

由于所有懒狗的主人都是这么想的,所以在第 G [ k ] G[k] G[k] 天,所有狗会一起被崩掉。

显然,因为 G [ k ] = G [ k − 1 ] + 1 G[k]=G[k-1]+1 G[k]=G[k1]+1 G [ 1 ] = 1 G[1]=1 G[1]=1,所以可以得到 G [ k ] = k G[k]=k G[k]=k,所以有了上面的结论。

second

在上面的推导中,得到一个很重要的思想——如何判断自己的狗是否生了病:先假设自己的狗没生病,然后看看在这个假设下的最晚开枪时间,假如过了那个时间还没人开枪,那么就可以把自己的狗崩了。

为什么说是最晚开枪时间呢?因为对于一个狗主人而言,他并不能看到所有其它狗的状态,对于看不到的,他只好开动脑筋瞎 yy,将所有生病情况枚举出来,然后找出最晚的。

于是我们可以得到这样的 d p dp dp 方程:设 U U U 为当前狗的生病状态, d p [ U ] dp[U] dp[U] 表示生病状态为 U U U 时最早的开枪时间,那么设能转移到 U U U 的状态为 V V V,则有 d p [ U ] = max ⁡ { d p [ V ] } + 1 dp[U]=\max\{dp[V]\}+1 dp[U]=max{ dp[V]}+1。(这个方程不完全严谨,只是让大家大概明白意思,具体还是要看下面)

怎么枚举 V V V 呢:首先枚举出 U U U 中的一只懒狗的主人( O ( n ) O(n) O(n)),对于他看的出是否生病的狗,保持 U U U 中的状态不变,看不出的那些就随便枚举即可( O ( 2 n ) O(2^n) O(2n)),以及对于他自己,姑且要假设自己的狗不是懒狗。

枚举完所有狗主人的状态后,找出最早开枪的狗主人,他的开枪时间 + 1 +1 +1 就得到了 d p [ U ] dp[U] dp[U],假如有多个最早开枪的狗主人,那么被杀的狗也要相应增加。

但是有可能出现环,即 i i i 看不到 j j j j j j 也看不到 i i i,相互枚举成了死循环,这样他们是永远都不会开枪的,这个时候就不管他们了。怎么判环下面会讲。

由于枚举 U U U 又需要 O ( 2 n ) O(2^n) O(2n) 的时间,所以总的时间复杂度为 O ( 4 n n ) O(4^nn) O(4nn)

third

根据打(ti)表(jie)可知,如果满足 V ∈ U V\in U VU,那么一定有 d p [ V ] ≤ d p [ U ] dp[V]\leq dp[U] dp[V]dp[U]。(证明往下看就知道了)

回看上面的枚举,对于当前的 U U U 状态,其中的每个狗主人都要找到自己的最晚开枪时间,也就是要在他们 yy 出的状态中找一个最大值,根据这个规律,就可以得到,假如狗主人认为它看不到的狗都是懒狗,那么这样的状态的开枪时间就是最晚的。

于是少了一个 2 n 2^n 2n 的复杂度,此时时间复杂度为 O ( 2 n n ) O(2^nn) O(2nn)

forth

但这仍不足以让我们通过 n ≤ 3000 n\leq 3000 n3000 的部分,于是还需要找到更好的做法。

尝试建出这幅图的补图,即如果 i i i 看不到 j j j 的狗,那么 i i i j j j 连一条边。然后去掉其中的环以及能到达环的点,然后我们得到了一个 D A G DAG DAG,设这个 D A G DAG DAG 中有 t t t 个点。

可以发现,如果将上面的 d p dp dp 转移到这个模型上来的话,可以设每个点有一个颜色,黑色为有病,白色为没病,上面的每一次转移本质上为:假设自己的狗不是懒狗,看不到的都是懒狗。在这张图上就是:将一个黑点变白,然后将能连向的点全部变黑。

显然对于一个初始状态而言,最终的结果就是将状态中的黑点能到达的点全部变黑一次,然后又变白,而每次变白对应着一次转移,每次转移都要 + 1 +1 +1(对应上面的 d p [ U ] = max ⁡ { d p [ V ] } + 1 dp[U]=\max\{dp[V]\}+1 dp[U]=max{ dp[V]}+1),所以此时的开枪时间就是所有黑点能到达的点数。

这时再看上面的规律,证明就很简单了,增加黑点并不会产生负贡献,即增加一个黑点后黑点集能到达的点数不可能减少。

那问题就简单了:设一个点 x x x 能被 p p p 个点到达(包括自己),那么如果这 p p p 个点中但凡有一个黑点, x x x 都会产生贡献,方案数为 2 p − 1 2^p-1 2p1,然后其他的 t − p t-p tp 个点怎么样都不会影响到 x x x,所以 x x x 的总贡献为 ( 2 p − 1 ) × 2 t − p (2^p-1)\times 2^{t-p} (2p1)×2tp

剩下第二问也很好做,考虑什么时候点 x x x 的狗会被崩掉,显然是没有黑点能到达 x x x 时,即能到达 x x x 的点全都是白点,但是 x x x 要是黑点。

为什么呢?首先,我们知道,一个状态的开枪时间就是能到达的点数,往后推一步就是将一个黑点变成白点,将连向的点变成黑点,假如一个黑点不能被其它黑点到达,那么他往后推一步之后,得到的新状态能到达的点数就会 − 1 -1 1,反之,如果能被其它黑点到达,那么他往后推一步的话得到的新状态能到达的点数不变。

回看上面的 d p dp dp,只有最早开枪的狗主人才会被记入答案,而我们发现,一个狗主人往后推一步不是 − 1 -1 1 就是不变,那么显然,只有 − 1 -1 1 的那批狗主人的狗才会被干掉,而 − 1 -1 1 的条件正是没有其它黑点能到达自己。

剩下的问题就很简单了,由于其它 t − p t-p tp 个点怎么样都无所谓,所以方案数就是 2 t − p 2^{t-p} 2tp

b i t s e t bitset bitset 优化一下,时间复杂度就是 O ( n 3 32 ) O(\frac {n^3} {32}) O(32n3)

代码是抄的 yyb 大佬,去除环的部分并不需要 d f s dfs dfs 然后重新建图,只需要从出度为 0 0 0 的点一层层往上推即可,遇到环就停止,实现方法很妙,具体看代码吧:

#include <cstdio>
#include <bitset>
#include <algorithm>
using namespace std;
#define maxn 3010
#define mod 998244353

int n; char s[maxn];
bitset<maxn> a[maxn],g[maxn];
int du[maxn],S[maxn],t=0;
int two[maxn],ans1=0,ans2=0;

int main()
{
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
    
    
		scanf("%s",s+1);
		for(int j=1;j<=n;j++)
		if(i!=j)g[i][j]=s[j]=='0',du[i]+=g[i][j];//记录新图和出边
	}
	for(int i=1;i<=n;i++)if(!du[i])S[++t]=i;//S用来记录DAG中的点
	for(int i=1;i<=t;i++)
	for(int j=1;j<=n;j++)
	if(g[j][S[i]]&&!--du[j])S[++t]=j;//倒推
	
	for(int i=1;i<=t;i++)a[S[i]][S[i]]=1;
	//a[i]记录能到达点i的点的集合,a[i][j]=1表示j能到达i
	for(int i=t;i>=1;i--)
	for(int j=1;j<=n;j++)
	if(g[S[i]][j])a[j]|=a[S[i]];
	
	two[0]=1;
	for(int i=1;i<=n;i++)two[i]=2*two[i-1]%mod;
	for(int i=1,p;i<=t;i++)
	{
    
    
		p=a[S[i]].count();
		ans1=(1ll*ans1+(1ll*two[p]-1ll+mod)%mod*two[t-p]%mod)%mod;
		ans2=(1ll*ans2+1ll*two[t-p])%mod;
	}
	printf("%d %d\n",ans1,ans2);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/109611793
今日推荐