2017国家集训队作业Atcoder题目试做

2017国家集训队作业Atcoder题目试做

虽然远没有达到这个水平,但是据说Atcoder思维难度大,代码难度小,适合我这种不会打字的选手,所以试着做一做

不知道能做几题啊

在完全自己做出来的题前面打"√“(目前好像还没有诶。。。o(╥﹏╥)o)

计数器菌:9/104

agc001_d

如果两个字符确定相等就在中间连一条边,那么所有字符相同就等价于使整个图联通

然后发现至少要 n − 1 n-1 n1条边,而事实上一个序列贡献的边数最大为 n 2 \frac n 2 2n条,而且一旦序列里有一个奇数贡献的边数就会减去 1 2 \frac 1 2 21,所以如果原始序列出现 > 2 \gt 2 >2个奇数,那么就不可行

一个偶数序列,整体向左平移一个之后,正好全部连起来了

如果有奇数怎么办?因为至多两个奇数,我们把奇数放到两边,中间全是偶数,那么可以像刚才那样做,两边的奇数这样做也符合题意。

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const int maxn=200;
int n,m;
int a[maxn];

int main(){
#ifdef LZT
//	freopen("in","r",stdin);
#endif
	int num=0;
	n=read();m=read();
	rep(i,1,m){
		a[i]=read();
		if(a[i]&1) num++;
	}
	if(num>2){
		puts("Impossible");
		return 0;
	}
	for(int i=1;i<=m;i++)
		if(a[i]&1){
			if(a[1]&1) swap(a[i],a[m]);
			else swap(a[i],a[1]);
		}
	rep(i,1,m) cout<<a[i]<<' ';
	cout<<endl;
	a[1]++;a[m]--;
	if(a[m]==0) m--;
	if(m>=2){
		cout<<m<<endl;
		rep(i,1,m) cout<<a[i]<<' ';
		cout<<endl;
	}
	else{
		if(n<=2){
			cout<<1<<endl;
			cout<<n<<endl;
		}
		else{
			cout<<2<<endl;
			cout<<n-1<<' '<<1<<endl;
		}
	}
	return 0;
}

agc001_e

我们发现答案其实就是要求 ∑ i = 1 n − 1 ∑ j = i + 1 n C a i + a j + b i + b j a i + a j \sum_{i=1}^{n-1}\sum_{j=i+1}^nC_{a_i+a_j+b_i+b_j}^{a_i+a_j} i=1n1j=i+1nCai+aj+bi+bjai+aj

然后知道 C a i + a j + b i + b j a i + a j C_{a_i+a_j+b_i+b_j}^{a_i+a_j} Cai+aj+bi+bjai+aj实际上就是点 ( − a i , − b i ) (-a_i,-b_i) (ai,bi)走到 ( a j , b j ) (a_j,b_j) (aj,bj)的方案数

那么原式等价于求点集 ( − a i , − b i ) (-a_i,-b_i) (ai,bi)到点集 ( a i , b i ) (a_i,b_i) (ai,bi)两两的方案数的和减去所有点走到他对应的对称点的方案数(即 i = j i=j i=j的方案数)除以2(每个方案被算了两次)

所以dp就可以了,可以想象中建立一个超级源点连向所有的 ( − a i , − b i ) (-a_i,-b_i) (ai,bi)和超级汇点连向所有的 ( a i , b i ) (a_i,b_i) (ai,bi),就可以求出方案数

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const int mod=1000000007;
const int maxn=200200;
const int maxm=2200;
int n;
int a[maxn],b[maxn];
int dp[maxm*2][maxm*2];
int flag[maxm*2][maxm*2];
int ans;

void pl(int &a,ll b){
	a=(a+b%mod)%mod;
}
void mi(int &a,ll b){
	a=a-b%mod;
	while(a<0) a+=mod;
	a=a%mod;
}

int main(){
#ifdef LZT
	freopen("in","r",stdin);
#endif
	n=read();
	rep(i,1,n) a[i]=read(),b[i]=read();
	rep(i,1,n){
		flag[2100-a[i]][2100-b[i]]++;
		flag[a[i]+2100][b[i]+2100]++;
	}
	rep(i,1,4200){
		rep(j,1,4200){
			pl(dp[i][j],dp[i-1][j]);
			pl(dp[i][j],dp[i][j-1]);
			if(flag[i][j] && i<=2100 && j<=2100) pl(dp[i][j],flag[i][j]);
			if(flag[i][j] && i>=2100 && j>=2100) pl(ans,dp[i][j]*1ll*flag[i][j]);
		}
	}
	memset(dp,0,sizeof(dp));
	dp[0][0]=1;
	rep(i,0,4200){
		rep(j,0,4200){
			if(i==0 && j==0) continue;
			if(i) pl(dp[i][j],dp[i-1][j]);
			if(j) pl(dp[i][j],dp[i][j-1]);
		}
	}
	rep(i,1,n)
		mi(ans,dp[a[i]+a[i]][b[i]+b[i]]);
	ans=ans*500000004ll%mod;
	cout<<ans<<endl;
	return 0;
}

agc002_d

先考虑暴力做法,对于一组询问 ( x , y , z ) (x,y,z) (x,y,z),我们暴力将边从小到大加入图里,当 x x x所在的连通块点数加 y y y所在连通块点数(当 x x x y y y在不同连通块时才加)第一次 ≥ z \geq z z时,当前边的序号就是答案

所以答案是有单调性的,可以二分

然后每一组都二分肯定不行,所以整体二分

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const int maxn=100100;
int n,m,Q;
pii edge[maxn];
struct query
{
	int ind;
	int a,b,num;
	void re(int x){
		a=read(),b=read(),num=read();
		ind=x;
	}
} q[maxn],tmp[maxn];
int ans[maxn],fa[maxn],sz[maxn];
bool ok[maxn];
pii sta[maxn];int cnt;

inline int fp(int x){if(x==fa[x]) return x;return fp(fa[x]);}

void solve(int l,int r,int le,int ri){
	//cout<<l<<' '<<r<<' '<<le<<' '<<ri<<endl;
	if(l==r){
		rep(i,le,ri) ans[q[i].ind]=l;
		int x=edge[l].fi,y=edge[l].se;
		x=fp(x);y=fp(y);
		if(x!=y){
			if(sz[x]>sz[y])swap(x,y);
			fa[x]=y;sz[y]+=sz[x];
		}
		return;
	}
	int md=(l+r)>>1;cnt=0;
	rep(i,l,md){
		int x=edge[i].fi,y=edge[i].se;
		x=fp(x);y=fp(y);
		if(x!=y){
			if(sz[x]>sz[y]) swap(x,y);
			fa[x]=y;sz[y]+=sz[x];
			sta[++cnt]=mp(x,y);
		}
	}
	rep(i,le,ri){
		query &nw=q[i];
		int a=nw.a,b=nw.b;
		//cout<<a<<' '<<b<<endl;
		a=fp(a);b=fp(b);
		//cout<<i<<' '<<a<<' '<<b<<' ';
		int nww=0;
		if(a==b) nww=sz[a];else nww=sz[a]+sz[b];
		if(nww>=nw.num) ok[i]=1;else ok[i]=0;
		//cout<<ok[i]<<endl;
	}
	int pos=le-1;
	rep(i,le,ri)
		if(ok[i]) tmp[++pos]=q[i];
	pos=ri+1;
	rrep(i,ri,le)
		if(!ok[i]) tmp[--pos]=q[i];
	//cout<<le<<' '<<ri<<' '<<pos<<endl;
	rep(i,le,ri) q[i]=tmp[i];
	while(cnt){
		int x=sta[cnt].fi,y=sta[cnt].se;
		fa[x]=x;sz[y]-=sz[x];cnt--;
	}
	solve(l,md,le,pos-1);solve(md+1,r,pos,ri);
}

int main(){
	n=read(),m=read();
	rep(i,1,m) edge[i].fi=read(),edge[i].se=read();
	Q=read();
	rep(i,1,Q) q[i].re(i);
	rep(i,1,n) sz[i]=1,fa[i]=i;
	solve(1,m,1,Q);
	rep(i,1,Q) printf("%d\n",ans[i]);
	return 0;
}

/*
5 6
2 3
4 5
1 2
1 3
1 4
1 5
6
2 4 3
2 4 4
2 4 5
1 3 3
1 3 4
1 3 5
*/

agc002_e

真难想的博弈题

首先先想状态

不知怎么想到把他表示成图形

就是我们先排序 然后把一堆石子想象成一个石子个数*1的矩形。 把矩形从高到低排列变成一个图形。

然后操作就变成了删掉最左边一列或者最下面一行

假设有一个点当前在 ( 1 , 1 ) (1,1) (1,1),那么每次操作他向右或者向上移动一个,不能移动者输

那么给每个点标记上 o o o或者 x x x,分别表示必胜和必败

所有最外层的角上(意会)一定都是 x x x

然后发现当 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1)不是最外层的点的时候, ( x , y ) (x,y) (x,y) ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1)的标记相同

所以算法就是先把 ( 1 , 1 ) (1,1) (1,1)向右上方移动直到边界为止,然后要么向上要么向右,如果都是必败那么就是必败,否则必胜

向上向右因为只有一个方向所以只奇偶性有关

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const int maxn=100100;
int n;
int a[maxn];

int main(){
	n=read();
	rep(i,1,n) a[i]=read();
	sort(a+1,a+n+1);
	reverse(a+1,a+n+1);
	rep(i,1,n+1){
		if(a[i]<i){
			i--;
			int num=0;
			for(int j=i+1;j<=n;j++)
				if(a[j]==i) num++;
			if(num&1){
				puts("First");
				return 0;
			}
			if((a[i]-i)&1) puts("First");
			else puts("Second");
			return 0;
		}
	}
}

agc002_f

直接 dp \text{dp} dp

把“把每种颜色的最左边的球变为0”理解成“任意前缀的颜色数<0的数量”

然后放球的时候一次把一种颜色的球放完,把“放在后面”变成“插入这么多个新的球”,放完一种颜色的 k − 1 k-1 k1 个球之后才能放 0 0 0

然后 f [ i ] [ j ] f[i][j] f[i][j] 表示当前放前 i i i 种颜色(我们固定先放颜色1,再放颜色2,最后乘上 fac[i] \text{fac[i]} fac[i] 就可以了),还没放的 0 \text{0} 0 的个数是 j j j 的方案数

那么首先可以在最前面加一个 0 0 0 ,即 f [ i ] [ j ] = f [ i ] [ j + 1 ] f[i][j]=f[i][j+1] f[i][j]=f[i][j+1]

然后可以新增一种颜色,从 f [ i − 1 ] [ j − 1 ] f[i-1][j-1] f[i1][j1] 转移过来,我们知道当前的球的个数是 ( i − 1 ) ∗ k − ( j − 1 ) (i-1)*k-(j-1) (i1)k(j1) 个,要放入 k − 1 k-1 k1 个新的球,运用插板法,就是 C ( i − 1 ) ∗ k − ( j − 1 ) + k − 1 − 1 ( i − 1 ) ∗ k − j = C i ∗ k − j − 1 k − 2 C_{(i-1)*k-(j-1)+k-1-1}^{(i-1)*k-j}=C_{i*k-j-1}^{k-2} C(i1)k(j1)+k11(i1)kj=Cikj1k2 种方案

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const int mod=1e9+7;
const int maxn=3050;
int n,k;
ll f[maxn][maxn];
ll fac[maxn*maxn],inv[maxn*maxn];

inline int C(int x,int y){
	if(x<y) return 0;
	return fac[x]*inv[y]%mod*inv[x-y]%mod;
}

int main(){
	n=read(),k=read();
	if(k==1){
		puts("1");
		return 0;
	}
	fac[0]=1;
	rep(i,1,3000*3000) fac[i]=fac[i-1]*i%mod;
	inv[0]=inv[1]=1;
	rep(i,2,3000*3000) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	rep(i,1,3000*3000) inv[i]=inv[i]*inv[i-1]%mod;
	f[0][0]=1;
	rep(i,1,n){
		rrep(j,i,0){
			f[i][j]=f[i][j+1]%mod;
			if(j){
				f[i][j]+=f[i-1][j-1]*C(i*k-j-1,k-2)%mod;
				f[i][j]%=mod;
			}
		}
	}
	cout<<f[n][0]*fac[n]%mod;
	return 0;
}

agc003_d

首先把每个数分解质因数,然后把指数模3,称变换之后的数为原来的数的最简数

对于一个最简数,存在一个补数,定义为与最简数相乘是立方数的最小数

对于一个最简数代表的数集和其补数代表的数集,我们选取较大的一个

如果一个数和其补数相同,那么只能选1个

关键在于分解质因数,我们先筛出来3000以内的质数,然后如果一个数把3000以下的数除完之后不为1,那么肯定是完全平方数或者质数,分别判断一下即可(如果是两个3000以上的质数相乘,那么不可能对答案产生贡献,所以可以忽略)

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register ll i=(ll)(j);i<=(ll)(k);i++)
#define rrep(i,j,k) for(register ll i=(ll)(j);i>=(ll)(k);i--)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const ll maxn=100100;
int n;
ll a[maxn];
bool isp[maxn];
int pr[maxn],cnt;
int num[maxn][500];
set<ll> s;
map<ll,int> M;
map<ll,int> ind;
map<ll,int> used;

void init(){
	memset(isp,1,sizeof(isp));
	isp[0]=isp[1]=0;
	rep(i,2,3000){
		if(isp[i]){
			pr[++cnt]=i;
		}
		for(ll j=1;j<=cnt && i*pr[j]<=3000;j++){
			isp[i*pr[j]]=0;
			if(i%pr[j]==0) break;
		}
	}
}

bool pd(ll x){
	for(ll i=2;i*i<=x;i++){
		if(x%i==0) return 0;
	}
	return 1;
}

int main(){
#ifdef LZT
	//freopen("in","r",stdin);
#endif
	ll ans=0;
	n=read();
	rep(i,1,n) a[i]=read();
	init();
	rep(i,1,n){
		ll t=a[i];
		rep(j,1,cnt){
			while(t%pr[j]==0){
				t=t/pr[j];
				num[i][j]++;
			}
			num[i][j]%=3;
		}
		if(t>pr[cnt]){
			ll nw=sqrt(t);
			if(nw*nw==t){
				num[i][cnt+1]=nw;
				num[i][cnt+2]=2;
				t=1;
			}
			else{
				if(t<=100000 && pd(t)){
					num[i][cnt+1]=t;
					num[i][cnt+2]=1;
					t=1;
				}
			}
		}
		ll tt=1;
		rep(j,1,cnt){
			if(num[i][j]==1) tt=tt*pr[j];
			else if(num[i][j]==2) tt=tt*pr[j]*pr[j];
		}
		if(num[i][cnt+1]){
			tt=tt*num[i][cnt+1];
			if(num[i][cnt+2]==2) tt=tt*num[i][cnt+1];
		}
		if(t==1){
			s.insert(tt);
			M[tt]++;
			ind[tt]=i;
		}
		else ans++;
	}
	for(set<ll>::iterator it=s.begin();it!=s.end();it++){
		ll nw=*it;
		if(used[nw]) continue;
		used[nw]=1;
		ll i=ind[nw];
		ll t=1;
		for(ll j=1;j<=cnt;j++){
			if(num[i][j]==1) t=t*pr[j]*pr[j];
			else if(num[i][j]==2) t=t*pr[j];
		}
		if(num[i][cnt+1]){
			if(num[i][cnt+2]==1) t=t*num[i][cnt+1]*num[i][cnt+1];
			else t=t*num[i][cnt+1];
		}
		if(nw==t) ans++;
		else ans+=max(M[nw],M[t]);
		used[t]=1;
	}
	cout<<ans<<endl;
	return 0;
}

agc003_e

借鉴了 fizzydavid \text{fizzydavid} fizzydavid的代码,写的很精巧

题目就是说有一个数字串S,初始长度为n,是1 2 3 4 …… n,有m次操作,每次操作给你一个正整数a[i],你先把S无穷重复,然后把前a[i]截取出来成为新的S。
求m次操作后,每个数字在S中出现的次数。

我们倒着考虑

首先如果 a [ i ] ≥ a [ i + 1 ] a[i] \ge a[i+1] a[i]a[i+1] 那么 a [ i ] a[i] a[i] 可以直接删掉,显然没用

所以我们先用单调栈把 a a a 变成单调递增的

然后每一次操作等价于把前一次的序列复制几次然后加上一个前缀

我们倒着考虑,用一个 pair \text{pair} pair 表示我们当前给长度为 pair.first \text{pair.first} pair.first 的前缀重复加了 pair.second \text{pair.second} pair.second

那么一开始,考虑最后一次操作,等价于插入 pair&lt;a[cnt],1&gt; \text{pair&lt;a[cnt],1&gt;} pair<a[cnt],1> 就是整个加了1次

然后考虑当前操作的长度为 c u r cur cur ,对于每一个存在的 pair \text{pair} pair ,我们会将其更新,因为实际上这个 p a i r pair pair 可以由当前的 cur \text{cur} cur 得到,所以我们计算当前的 c u r cur cur 这个前缀在每一个存在的 pair \text{pair} pair 中一共出现几次,也就是

sum+=nw.se*(nw.fi/cur);

当然如果 n w . f i nw.fi nw.fi 不是 c u r cur cur 的倍数,那么会出现一个多余的小于 c u r cur cur 的前缀,这个前缀出现了 n w . s e nw.se nw.se 次,长度就是 KaTeX parse error: Expected '}', got 'EOF' at end of input: …text{nw.fi%cur}

然后我们使用partial sum的方法,最后倒着加起来就好了

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

priority_queue<pair<ll,ll> > pq;
ll n,q;
ll st[100100],cnt;
ll ans[100100];
int main(){
#ifdef LZT
//	freopen("in","r",stdin);
#endif
	n=read(),q=read();
	st[++cnt]=n;
	for(int i=1;i<=q;i++){
		ll x=read();
		while(st[cnt]>=x) cnt--;
		st[++cnt]=x;
	}
	pq.push(mp(st[cnt],1));
	for(int i=cnt;i>=1;i--){
		ll sum=0,cur=st[i];
		while(!pq.empty() && pq.top().fi>cur){
			pair<ll,ll> nw=pq.top();pq.pop();
			sum+=nw.se*(nw.fi/cur);
			if(nw.fi%cur) pq.push(mp(nw.fi%cur,nw.se));
		}
		pq.push(mp(cur,sum));
	}
	while(!pq.empty()) ans[pq.top().fi]+=pq.top().se,pq.pop();
	rrep(i,n,1) ans[i]+=ans[i+1];
	rep(i,1,n) printf("%lld \n",ans[i]);
	return 0;
}

agc003_f

Atcoder \text{Atcoder} Atcoder 的题果然都是神仙

{ b c 0 d } k − 1 \left\{ \begin{matrix} b &amp; c \\ 0 &amp; d \end{matrix} \right\} ^ {k-1} {b0cd}k1 之后 b − c b-c bc 就是答案

下面来分析一波:

首先定义 纵向相邻横向相邻

纵向相邻 指一个初始图案存在一列,满足这一列的第一行和最后一行两个格子都是 #

类似的,横向相邻 指一个初始图案存在一行,满足这一行的第一列和最后一列两个格子都是 #

不难发现,如果一个初始图案既 纵向相邻横向相邻,那么他复制多次之后仍旧联通

如果一个初始图案既不 纵向相邻 也不 横向相邻,那么他复制多次之后的联通块数就是黑色格子数的 k − 1 k-1 k1 次,因为复制之后不会有连通块合并的情况

剩下的就是只满足其中一个的情况。不失一般性,我们假设初始图案满足 纵向相邻,不满足 横向相邻

考虑当前在 k − 1 k-1 k1 层,我们操作一次之后到第 k k k 层,中间每个量的变化

a k a_{k} ak 表示第 k k k 层的连通块个数, b k b_k bk 表示第 k k k 层的黑色格子数量, c k c_k ck 表示第 k k k 层的图中有多少个上下相邻且都为黑色的格子对, d k d_k dk 表示第 k k k 层的图中有多少满足 纵向相邻 的列

那么我们来考虑 k − 1 k-1 k1 k k k 的变化

a k = b k − 1 − c k − 1 , b k = b k − 1 2 , c k = b k − 1 ⋅ c k − 1 + c k − 1 ⋅ d k − 1 , d k = d k − 1 2 a_k=b_{k-1}-c_{k-1},b_k=b_{k-1}^2,c_k=b_{k-1} \cdot c_{k-1}+c_{k-1} \cdot d_{k-1},d_k=d_{k-1}^2 ak=bk1ck1,bk=bk12,ck=bk1ck1+ck1dk1,dk=dk12

惊讶的发现,他可以用一个矩阵完美地套进去!

首先 a a a 这个玩意根本没有用,不记录

考虑 2 × 2 2\times 2 2×2 的矩阵乘法

KaTeX parse error: Expected '\right', got '\matrix' at position 8: \left\{\̲m̲a̲t̲r̲i̲x̲{a & b\\c&d}\ri…

那么

KaTeX parse error: Expected '\right', got '\matrix' at position 8: \left\{\̲m̲a̲t̲r̲i̲x̲{b_{k-1} & c_{k…

然后就做完了。。。

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)
#define Debug(...) fprintf(stderr, __VA_ARGS__)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const int maxn=1010;
const int mod=1e9+7;
int n,m;ll k;
char c[maxn][maxn];
char tc[maxn][maxn];

struct Matrix{
	int a[3][3];
	Matrix(){memset(a,0,sizeof(a));}
	void init_one(){a[1][1]=a[2][2]=1;}
	Matrix operator * (const Matrix &b) const{
		Matrix ret;
		rep(i,1,2) rep(j,1,2) rep(k,1,2){
			ret.a[i][j]+=(a[i][k]*1ll*b.a[k][j])%mod;
			if(ret.a[i][j]>=mod) ret.a[i][j]-=mod;
		}
		return ret;
	}
	void pr(){
		cout<<a[1][1]<<' '<<a[1][2]<<endl<<a[2][1]<<' '<<a[2][2]<<endl<<endl;
	}
} a;

Matrix ksm(Matrix a,ll p){
	Matrix ret;ret.init_one();
	while(p){
		if(p&1) ret=ret*a;
		p>>=1;
		a=a*a;
	}
	return ret;
}

int ksm(int x,ll p){
	int ret=1;
	while(p){
		if(p&1) ret=ret*1ll*x%mod;
		p>>=1;
		x=x*1ll*x%mod;
	}
	return ret;
}

void work(){
	n=read(),m=read(),k=read();
	int cnt=0;bool f1=0,f2=0;
	rep(i,1,n) rep(j,1,m){
		c[i][j]=getchar();
		while(c[i][j]!='.' && c[i][j]!='#') c[i][j]=getchar();
	}
	rep(i,1,n) if(c[i][1]=='#' && c[i][m]=='#') f1=1;
	rep(j,1,m) if(c[1][j]=='#' && c[n][j]=='#') f2=1;
	if(f2 && !f1){
		rep(i,1,n) rep(j,1,m) tc[j][n+1-i]=c[i][j];
		swap(n,m);
		rep(i,1,n) rep(j,1,m) c[i][j]=tc[i][j];
	}
	rep(i,1,n){
		if(c[i][1]=='#' && c[i][m]=='#') a.a[2][2]++;
		rep(j,1,m){
			if(c[i][j]=='#') a.a[1][1]++;
			if(c[i][j]=='#' && c[i][j-1]=='#') a.a[1][2]++;
		}
	}
	if(!f1 && !f2){
		printf("%d\n",ksm(a.a[1][1],k-1));
		return;
	}
	else if(f1 && f2){
		puts("1");
		return;
	}
	a=ksm(a,k-1);
	printf("%d\n",(a.a[1][1]-a.a[1][2]+mod)%mod);
}

int main(){
	#ifdef LZT
		freopen("in","r",stdin);
	#endif
	
	work();
	
	#ifdef LZT
		Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
	#endif
}

agc004_c

去年南外校赛原题,当时不会。。。o(╥﹏╥)o

直接构造,第一列涂红,最后一列涂蓝,剩下的格子奇数行涂红,偶数行涂蓝,原来是紫色的格子都涂上,一定可以

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)
#define Debug(...) fprintf(stderr, __VA_ARGS__)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const int maxn=550;
int n,m;
char c[maxn][maxn];

void work(){
	n=read(),m=read();
	rep(i,1,n) rep(j,1,m){
		c[i][j]=getchar();
		while(c[i][j]!='.' && c[i][j]!='#') c[i][j]=getchar();
	}
	rep(i,1,n){
		c[i][1]='R',c[i][m]='B';
		if(i&1){rep(j,2,m-1) if(c[i][j]=='.') c[i][j]='R';}
		else{rep(j,2,m-1) if(c[i][j]=='.') c[i][j]='B';}
	}
	rep(i,1,n){
		rep(j,1,m) if(c[i][j]!='B') putchar('#'); else putchar('.');
		puts("");
	}
	puts("");

	rep(i,1,n){
		rep(j,1,m) if(c[i][j]!='R') putchar('#'); else putchar('.');
		puts("");
	}
}

int main(){
	#ifdef LZT
		freopen("in","r",stdin);
	#endif
	
	work();
	
	#ifdef LZT
		Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
	#endif
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值