Codeforces 1103 简要题解

文章目录

  • A题
  • B题
  • C题
  • D题

传送门
又一场原地爆炸的比赛。

A题

传送门 简单思维题
题意:给一个 4 ∗ 4 4*4 44的格子图和一个01串,你要根据01串放 1 ∗ 2 1*2 12的木块,如果是0就竖放一个,是1就横放一个,一行或者一列满了可以直接消掉。
现在让你每次输出放下木块的坐标,并保证所有操作中没有木块相交。


思路:
可以直接按照自己的思路模拟然后我这个sb想了好久用什么策略
下来之后突然想到一个更简单的做法,我们保证如果竖放先放 ( 1 , 1 ) (1,1) (1,1),如果横放先放 ( 4 , 4 ) (4,4) (4,4),这样接下来无论是横放还是竖放都会消掉之前放过的一个。
比赛代码(丑:

#include
#define ri register int
#define fi first
#define se second
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
char s[1005];
int n,vis[5][5];
inline void update(){
	for(ri i=1;i<=4;++i)if(vis[i][1]&&vis[i][2]&&vis[i][3]&&vis[i][4])vis[i][1]=vis[i][2]=vis[i][3]=vis[i][4]=0;
	for(ri i=1;i<=4;++i)if(vis[1][i]&&vis[2][i]&&vis[3][i]&&vis[4][i])vis[1][i]=vis[2][i]=vis[3][i]=vis[4][i]=0;
}
inline void find(int type){
	if(!type){
		for(ri j=1;j<=4;++j)for(ri i=1;i<=3;++i){
			if(!vis[i][j]&&!vis[i+1][j]){
				vis[i][j]=vis[i+1][j]=1;
				cout<<i<<' '<<j<<'\n';
				return update();
			}
		}
	}
	else{
		for(ri i=1;i<=4;++i)for(ri j=1;j<=3;++j){
			if(!vis[i][j]&&!vis[i][j+1]){
				vis[i][j]=vis[i][j+1]=1;
				cout<<i<<' '<<j<<'\n';
				return update();
			}
		}		
	}
}
int main(){
	scanf("%s",s+1),n=strlen(s+1);
	for(ri i=1;i<=n;++i)find(s[i]-'0');
	return 0;
}

B题

传送门 二分答案
题意简述:你需要通过不超过60次询问来猜一个数 a , a ≤ 1 e 9 a,a\le1e9 a,a1e9,每次可以问两个数 x , y x,y x,y,如果 ( x m o d    a ) ≥ ( y m o d    a ) (x\mod a)\ge (y\mod a) (xmoda)(ymoda)会返回 0 0 0否则返回 1 1 1


思路:
我们考虑倍增求出 a a a的范围然后再二分求 a a a的准确值。
最开始先让 x = 0 , y = 1 x=0,y=1 x=0,y=1然后问,如果返回 1 1 1就说明 a a a不在区间 ( x , y ] (x,y] (x,y]里,那么令 x = y , y = y ∗ 2 + 1 x=y,y=y*2+1 x=y,y=y2+1继续问下去知道返回 0 0 0位置。
返回 0 0 0时说明 a a a的取值在 ( x , y ] (x,y] (x,y]之间,那么我们二分 a a a的值一直问 x , m i d x,mid x,mid来缩小范围即可。
代码:

#include
#define ri register int
#define fi first
#define se second
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
char s[6];
typedef long long ll;
int main(){
	while(scanf("%s",s)){
		if(s[0]=='e')break;
		if(s[0]=='m')break;
		ll x=0,y=1;
		while(cout<<"? "<<x<<' '<<y<<endl,fflush(stdout),scanf("%s",s),s[0]=='y'){
			x=y,y=x*2+1;
		}
		if(s[0]=='e')break;
		ll l=x+1,r=y,ans=y;
		while(l<=r){
			ll mid=l+r>>1;
			bool f;
			cout<<"? "<<x<<' '<<mid<<endl,fflush(stdout),scanf("%s",s),f=s[0]=='x';
			if(s[0]=='e')break;
			if(f)r=mid-1,ans=mid;
			else l=mid+1;
		}
		cout<<"! "<<ans<<endl;
		fflush(stdout);
	}
	return 0;
}

C题

传送门 结论+构造
题意简述:给一张无重边自环的无向图,每个点度数至少为3,现在给出 k k k,要求你构造出以下两类答案中的任意一种:

  1. 找到一条路径,使得其长度 ≥ n k \ge\frac nk kn
  2. 找到 k k k个环,使得每个环长度至少为 3 3 3且不为 3 3 3的倍数,且每个环中至少有一个点在所有输出的环中只出现过一次。

思路:
我们先 d f s dfs dfs构造出图的一个生成树,这个时候如果闲的胃疼可以求一遍直径去构造第一类答案,也可以直接枚举每个点的深度来判断能不能简单构造,如果不行就直接去构造多个环的答案。
然后考虑如何构造出多个环的答案,在生成树上面每个数的深度都不能满足条件的时候说明至少有 k k k个 叶子结点,由于每个点度数至少为 3 3 3,因此这个叶子结点除了连接父亲的树边以外还存在跟自己祖先连接的两条非树边 注意这里说的是祖先,因为如果不是祖先的话dfs的时候会走过去,它就不是叶子节点了
然后说明至少有两个环,且这个叶子结点可以当做那个在所有环中只出现过一次的特殊点。
然后可以用如下方法构造答案。

  1. 两个环中至少有一个长度不是 3 3 3的倍数,直接输出这个环。
  2. 两个环的长度都是 3 3 3的倍数,那么设离当前叶子结点近的点是 y y y,原的是 x x x,叶子结点是 z z z,我们把 y − > x y->x y>x路径上所有点和 z z z拿来构造成一个环,这个环的长度是 3 k + 1 3k+1 3k+1满足题意。

代码(略丑:

#include
#define ri register int
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
const int N=250005,M=5e5+5;
int n,m,k,dep[N],fa[N];
bool leaf[N],vis[N];
vector<int>e[N];
void dfs(int p){
	leaf[p]=vis[p]=1;
	for(ri i=0,v;i<e[p].size();++i){
		if(vis[v=e[p][i]])continue;
		leaf[p]=0,dep[v]=dep[p]+1,fa[v]=p,dfs(v);
	}
}
int main(){
	n=read(),m=read(),k=read();
	for(ri i=1,u,v;i<=m;++i)u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
	dep[1]=1,dfs(1);
	int tmp=(n+k-1)/k;
	for(ri i=1;i<=n;++i){
		if(dep[i]>=tmp){
			puts("PATH");
			printf("%d\n",dep[i]);
			while(i){
				printf("%d ",i);
				i=fa[i];
			}
			return 0;
		}
	}
	puts("CYCLES");
	vector<int>pos;
	for(ri i=1;i<=n&&pos.size()<k;++i)if(leaf[i])pos.push_back(i);
	for(ri i=1,p;i<=k;++i){
		bool f=0;
		p=pos[i-1];
		for(ri j=0,v;j<e[p].size();++j){
			if((v=e[p][j])!=fa[p]&&(dep[p]-dep[v]+1)%3){
				printf("%d\n",dep[p]-dep[v]+1),f=1;
				while(fa[p]!=fa[v]){
					printf("%d ",p);
					p=fa[p];
				}
				printf("%d",p);
				puts("");
				break;
			}
		}
		if(f)continue;
		vector<int>bad;
		for(ri j=0,v;j<e[p].size()&&bad.size()<2;++j)if((v=e[p][j])!=fa[p])bad.push_back(v);
		int x=bad[0],y=bad[1];
		if(dep[x]>dep[y])swap(x,y);
		printf("%d\n",dep[y]-dep[x]+2);
		printf("%d ",p);
		while(fa[x]!=fa[y]){
			printf("%d ",y);
			y=fa[y];
		}
		printf("%d",y);
		puts("");
	}
	return 0;
}

D题

传送门 状压dp好题
题意简述:给 n n n个二元组和一个 k k k
二元组 ( a i , e i ) (a_i,e_i) (ai,ei)表示第 i i i个位置的权值是 a i a_i ai,贡献是 e i e_i ei
现在对于每个位置可以让它的权值除以它自己一个不超过 k k k的约数,要求从 n n n个数中选择若干个数出来,使得它们的权值在除以约数过后的 g c d gcd gcd 1 1 1,花费的代价是选出来的选择数的个数乘上选出来的所有数的贡献和。
数据范围: n ≤ 1 e 5 , k , a i ≤ 1 e 12 n\le1e5,k,a_i\le1e12 n1e5,k,ai1e12


思路:
考虑到 g c d gcd gcd的范围也是 [ 1 , 1 e 12 ] [1,1e12] [1,1e12],不难证明这个 g c d gcd gcd最多有不超过 12 12 12个不同的质因子。
先思考一个问题:怎么去除自己的约束最优?
我们先令 g c d = a 1 p 1 a 2 p 2 . . . a k p k gcd=a_1^{p_1}a_2{p^2}...a_k^{p_k} gcd=a1p1a2p2...akpk
显然对于一个要计入答案的数 v a l val val,只用关心它和 g c d gcd gcd都有的质因数。
所以令 v a l = t ∗ a 1 q 1 a 2 q 2 . . . a k q k val=t*a_1^{q_1}a_2^{q_2}...a_k^{q_k} val=ta1q1a2q2...akqk,其中 ( t , a 1 ) = ( t , a 2 ) = . . . = ( t , a k ) = 1 (t,a_1)=(t,a_2)=...=(t,a_k)=1 (t,a1)=(t,a2)=...=(t,ak)=1
那么对于这个数,我们选择它肯定就要它自己使得若干个幂次不为 0 0 0的质数幂全部去掉,否则不如不选它,即去掉 a 1 q 1 , a 2 q 2 , . . . , a k q k a_1^{q_1},a_2^{q_2},...,a_k^{q_k} a1q1,a2q2,...,akqk中的至少一个 这里去掉的意思指的是用除法除掉,这样之后取gcd的时候如果选这个数这个被除掉的质数幂就不会算进去
这样一来,我们选出来的每个数至少会去掉一种之前没出现过的质数幂,因此最多选择不超过12个数就能够使得它们在除去质因子之后的 g c d gcd gcd为1。
于是可以设计状态 f i , j f_{i,j} fi,j表示现在已经去除的质因子的集合为 i i i,用了 j j j个数。
考虑先把所有相同的数并在一起(因为它们分解之后也是相同的)
转移的话可以先对于每个数预处理出另外一个 d p dp dp数组 g g g g i g_i gi表示对于这个数要使得被除去的质数幂状态为 i i i所需的最小这个数的个数(因为把相同的并在一起所以每个数的个数不一定是1)。
然后再枚举子集转移就行了。
细节较多。
代码:

#include
#define ri register int
using namespace std;
typedef long long ll;
inline ll read(){
	ll ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
inline map<ll,int> solve(ll x){
	map<ll,int>ret;
	ret.clear();
	for(ll i=2;i*i<=x;++i)while(x%i==0)x/=i,++ret[i];
	if(x^1)++ret[x];
	return ret;
}
const ll inf=1e18;
const int N=(1<<12)+5,M=13,K=1e6+5;
int n,m;
ll k,a[K],b[K],gc,res=inf;
int main(){
	n=read(),k=read(),gc=0;
	for(ri i=1;i<=n;++i)gc=__gcd(a[i]=read(),gc);
	for(ri i=1;i<=n;++i)b[i]=read();
	map<ll,int>fac=solve(gc);
	vector<ll>divv;
	for(map<ll,int>::iterator it=fac.begin();it!=fac.end();++it)divv.push_back(it->first);
	m=divv.size();
	vector<ll>v(m);
	map<vector<ll>,vector<ll> >mp;
	for(ri i=1;i<=n;++i){
		for(ri j=0;j<m;++j){
			v[j]=1;
			while(a[i]%divv[j]==0)a[i]/=divv[j],v[j]*=divv[j];
		}
		mp[v].push_back(b[i]);
	}
	vector<vector<ll> >f(1<<m,vector<ll>(m+1,inf));
	f[0][0]=0;
	for(map<vector<ll>,vector<ll> >::iterator it=mp.begin();it!=mp.end();++it){
		vector<ll>mul=it->first,sum=it->second,g(1<<m,inf);
		sort(sum.begin(),sum.end());
		for(ri i=1;i<sum.size();++i)sum[i]+=sum[i-1];
		for(ri i=0;i<(1<<m);++i){
			ll mult=1;
			for(ri j=0;j<m;++j)if((i>>j)&1)mult*=mul[j];
			if(mult<=k)g[i]=1;
		}
		for(ri i=0;i<(1<<m);++i)for(ri j=i;j;j=(j-1)&i){if(j==i)continue;g[i]=min(g[i],g[i^j]+g[j]);}
		for(ri i=(1<<m)-1;~i;--i)for(ri j=i;j;j=(j-1)&i){
			if(g[j]>sum.size())continue;
			for(ll k=0;g[j]+k<=m;++k)f[i][k+g[j]]=min(f[i][k+g[j]],f[i^j][k]+sum[g[j]-1]);
		}
	}
	for(ri i=0;i<=m;++i)if(f[(1<<m)-1][i]^inf)res=min(res,f[(1<<m)-1][i]*i);
	cout<<(res==inf?-1:res);
	return 0;
}

你可能感兴趣的:(#,题解)