Codeforces 1065 简要题解


传送门
G G G题略难,膜了一波 z h o u y u y a n g {\color{red} zhouyuyang} zhouyuyang巨佬的代码。
其余都挺清真的。


A题

传送门
题意:你有 s s s元钱,现在每颗糖 c c c元,每买 a a a颗会送你 b b b颗,问最多买几颗糖。


思路:按照题意模拟。
代码:

#include<bits/stdc++.h>
#define ri register int
#define int long long
#define fi first
#define se second
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
typedef long long ll;
signed main(){
	for(ri s,a,b,c,tt=read();tt;--tt){
		s=read(),a=read(),b=read(),c=read();
		cout<<(ll)s/c/a*(a+b)+((s-s/c/a*c*a)/c)<<'\n';
	}
	return 0;
}

B题

传送门
题意:给你 n n n个点 m m m条边让你随意构造无向图,称度为 0 0 0的点为孤立点,问孤立点数量的最小值和最大值。


思路:
要使得数量最小显然每两个点连一条边,数量最大的话显然尽量拿去建完全图,随便判一判就完了。
注意m读入可能会爆int!!!233333333333
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
typedef long long ll;
inline ll read(){
	ll ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
signed main(){
	ll n=read(),m=read();
	cout<<max(0ll,n-m*2)<<' ';
	for(ll i=0;;++i){
		if(i*(i-1)/2<m)continue;
		cout<<n-i;
		break;
	}
	return 0;
}

C题

传送门
题意:给你一排积木,没个积木有高度 h i h_i hi
让你每次指定一个高度把所有高于它的部分都砍掉,每次砍的代价是砍掉的所有高度之和,问如果每次砍的代价都不能超过给定的 k k k最少砍几次。


思路:考虑时间倒流,改为每次加一部分,预处理前缀和+二分求每次可以走的最上点即可。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
typedef long long ll;
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
typedef long long ll;
const int N=2e5+5;
int n,k,a[N],mn=0x3f3f3f3f,mx=-0x3f3f3f3f;
ll cnt[N],det[N];
signed main(){
	n=read(),k=read();
	for(ri i=1;i<=n;++i)a[i]=read();
	sort(a+1,a+n+1);
	for(ri i=1;i<=n;++i){
		++det[a[1]],--det[a[i]+1];
	}
	mn=a[1],mx=a[n];
	for(ri i=mn;i<=mx;++i)cnt[i]=cnt[i-1]+det[i];
	for(ri i=mn+1;i<=mx;++i)cnt[i]+=cnt[i-1];
	int turn=0;
	while(mn!=mx){
		++turn;
		int l=mn+1,r=mx,res=mn+1;
		while(l<=r){
			int mid=l+r>>1;
			if(cnt[mid]-cnt[mn]<=k)l=mid+1,res=mid;
			else r=mid-1;
		}
		mn=res;
	}
	cout<<turn;
	return 0;
}

D题

传送门
题意:给你一个 n ∗ n n*n nn的国际象棋,每个格子上有一个 1 1 1~ n 2 n^2 n2之间的正整数,每个格子上的数互不相同。你有三个棋子象,车和马,每次可以选择切换一种棋子或者走一步,问从 1 1 1所在格子走到 2 2 2所在格子再走到 3 3 3所在格子这样下去一直走到 n 2 n^2 n2所在格子所需的最短步数,以及在所有最短的方案中最小的换棋子的次数。


思路:直接暴力建边跑 n 2 n^2 n2次最短路即可。
注意车和象可以分别对行,列,主对角线,副对角线建虚点优化边数。
代码:

#include<bits/stdc++.h>
#define ri register int
#define fi first
#define se second
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
typedef pair<int,int> pii;
const int N=15,inf=0x3f3f3f3f;
int a[15][15],h[N],l[N],x[N<<1],y[N<<1],n,val[1005],tot;
struct Node{int v,w,col;};
vector<Node>e[1005];
inline void add(int u,int v,int w,int col){e[u].push_back((Node){v,w,col});}
map<pii,bool>in;
map<pii,pii>pred;
inline void solve(){
	static int hd,tl,s,t;
	static pii q[1005],dis[1005][4];
	dis[1][1]=pii(0,0),dis[1][2]=pii(0,0),dis[1][3]=pii(0,0);
	for(ri tt=1;tt<n*n;++tt){
		s=tt,t=tt+1;
		for(ri i=1;i<=tot;++i){
			if(i==s)continue;
			for(ri j=1;j<=3;++j)dis[i][j]=pii(inf,inf);
		}
		hd=1,tl=0;
		in[q[++tl]=pii(s,1)]=1,in[q[++tl]=pii(s,2)]=1,in[q[++tl]=pii(s,3)]=1;
		pred.clear();
		while(hd<=tl){
			pii x=q[hd++];
			in[x]=0;
			for(ri i=0,v,w,col;i<e[x.fi].size();++i){
				v=e[x.fi][i].v,w=e[x.fi][i].w,col=e[x.fi][i].col;
				pii tmp=pii(dis[x.fi][x.se].fi+w+val[v]+(x.se!=col),dis[x.fi][x.se].se+(x.se!=col));
				if(tmp<dis[v][col]){
					dis[v][col]=tmp;
					pred[pii(v,col)]=x;
					if(!in[pii(v,col)])in[q[++tl]=pii(v,col)]=1;
				}
			}
		}
	}
	pii res=min(min(dis[t][1],dis[t][2]),dis[t][3]);
	cout<<res.fi<<' '<<res.se<<'\n';
}
int main(){
	n=read();
	for(ri i=1;i<=n;++i)for(ri j=1;j<=n;++j)a[i][j]=read();
	for(ri i=1;i<=n;++i)for(ri j=1;j<=n;++j){
		if(i>1&&j>2)add(a[i][j],a[i-1][j-2],1,1);
		if(i>1&&j<n-1)add(a[i][j],a[i-1][j+2],1,1);
		if(i>2&&j>1)add(a[i][j],a[i-2][j-1],1,1);
		if(i>2&&j<n)add(a[i][j],a[i-2][j+1],1,1);
		if(i<n&&j>2)add(a[i][j],a[i+1][j-2],1,1);
		if(i<n&&j<n-1)add(a[i][j],a[i+1][j+2],1,1);
		if(i<n-1&&j>1)add(a[i][j],a[i+2][j-1],1,1);
		if(i<n-1&&j<n)add(a[i][j],a[i+2][j+1],1,1);
	}
	tot=n*n;
	for(ri i=1;i<=n;++i){
		val[h[i]=++tot]=1;
		for(ri j=1;j<=n;++j)add(a[i][j],h[i],0,2),add(h[i],a[i][j],0,2);
	}
	for(ri i=1;i<=n;++i){
		val[l[i]=++tot]=1;
		for(ri j=1;j<=n;++j)add(a[j][i],l[i],0,2),add(l[i],a[j][i],0,2);
	}
	for(ri det,i=1;i<=n*2-1;++i){
		val[x[i]=++tot]=1,det=i-n;
		for(ri j=1;j<=n;++j)if(j+det>=1&&j+det<=n)
		add(a[j][j+det],x[i],0,3),add(x[i],a[j][j+det],0,3);
	}
	for(ri sum,i=1;i<=n*2-1;++i){
		val[y[i]=++tot]=1,sum=i+1;
		for(ri j=1;j<=n;++j)if(sum-j>=1&&sum-j<=n)
		add(a[j][sum-j],y[i],0,3),add(y[i],a[j][sum-j],0,3);
	}
	solve();
	return 0;
}

E题

传送门
题意:给一个长度为n,字符集为A的字符串,以及m个数字b,对于任意数字 b i b_i bi足长度为 b i b_i bi的前缀和后缀先反转再交换位置后形成的新串与原串视作相等,问存在多少不同串。


思路:把 b b b排序,然后方案只跟起差分数列有关,显然对于两个长度都等于某一个 b i b_i bi的串 A , B A,B A,B如果 A &lt; B A&lt;B A<B就可以产生一次贡献。
于是只用处理询问一个 f ( x ) f(x) f(x)表示从长度为 x x x的所有串中选出来两个, A &lt; B A&lt;B A<B的方案。
代码:

#include<bits/stdc++.h>
#define ri register int
#define fi first
#define se second
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
typedef long long ll;
const int N=2e5+5,mod=998244353,inv2=499122177;
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
inline int ksm(int a,int p){int ret=1;for(;p;p>>=1,a=mul(a,a))if(p&1)ret=mul(ret,a);return ret;}
int n,m,a,b[N],ans;
inline int f(const int&x){int tmp=ksm(a,x);return mul(mul(tmp,tmp+1),inv2);}
int main(){
	n=read(),m=read(),a=read();
	for(ri i=1;i<=m;++i)b[i]=read();
	ans=mul(ksm(a,n-2*b[m]),f(b[1]));
	for(ri i=2;i<=m;++i)ans=mul(ans,f(b[i]-b[i-1]));
	cout<<ans;
	return 0;
}

F题

传送门
题意:给你一棵树。
你从根节点 1 1 1出发有如下两种走法:

  1. 走到该节点子树中的某个叶子
  2. 如果你在一个叶子上,可以选择走到你的 1 1 1~ k k k级祖先。

问你最后能够访问到的不同叶子数的最大值。


思路:
吐槽:想长链剖分和线段树想了好久然后突然发现一道NOIP难度的树形dp
g i g_i gi表示 i i i向子树中走且最后能回到 i i i的最大答案, f i f_i fi则表示 i i i向子树中最后不必回到 i i i的最大答案。
然后从儿子转移一下就完了。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
const int N=1e6+5;
int n,k,f[N],g[N],dep[N],mndep[N];
vector<int>e[N];
void dfs(int p){
	mndep[p]=0x3f3f3f3f;
	if(!e[p].size()){f[p]=g[p]=1,mndep[p]=dep[p];return;}
	for(ri i=0,v;i<e[p].size();++i){
		dep[v=e[p][i]]=dep[p]+1,dfs(v),mndep[p]=min(mndep[p],mndep[v]);
		if(mndep[v]-dep[p]<=k)g[p]+=g[v],f[p]+=g[v];
	}
	int sum=f[p];
	for(ri i=0,v;i<e[p].size();++i)f[p]=max(f[p],sum-(mndep[v=e[p][i]]-dep[p]<=k?g[v]:0)+f[v]);
}
int main(){
	n=read(),k=read();
	for(ri i=2;i<=n;++i)e[read()].push_back(i);
	dfs(1);
	cout<<max(f[1],g[1]);
	return 0;
}

G题

传送门
题意:给你一个字符串斐波那契数列, F 0 = F_0= F0=" 0 0 0", F 1 = F_1= F1=" 1 1 1", F i = F i − 2 + F i − 1 F_i=F_{i-2}+F_{i-1} Fi=Fi2+Fi1,令 A F i A_{F_i} AFi表示这个串的所有后缀排序后的序列,问这个序列第 k k k项的前 m m m个字符。


思路:
我们相当于二分一样每次按位考虑走 0 / 1 0/1 0/1就完了。
代码(照着 z h o u o y u y a n g {\color {red}zhouoyuyang} zhouoyuyang的码了一遍):

#include<bits/stdc++.h>
#define ri register int
using namespace std;
typedef long long ll;
const int N=305;
const ll lim=1e18+1;
int n,m;
ll k;
string ans="",f[15];
inline bool pre(int pos,const string&s,int st,const ll&len){
	int id=pos<=14?pos:14-(pos&1);
	return len<=f[id].size()&&!f[id].compare(0,len,s,st,len);
}
inline bool suf(int pos,const string&s,int st,const ll&len){
	int id=min(pos,14);
	return len<=f[id].size()&&!f[id].compare(f[id].size()-len,len,s,st,len);
}
inline ll calc(const int&n,const string&s){
	static ll g[N];
	for(ri i=0;i<=n;++i){
		if(i<2){g[i]=f[i]==s;continue;}
		g[i]=g[i-1]+g[i-2];
		for(ri up=s.size(),j=1;j<up;++j)if(suf(i-2,s,0,j)&&pre(i-1,s,j,up-j))++g[i];
		g[i]=min(g[i],lim);
	}
	return g[n];
}
int main(){
	f[0]="0",f[1]="1";
	for(ri i=2;i<=14;++i)f[i]=f[i-2]+f[i-1];
	scanf("%d%lld%d",&n,&k,&m);
	ll cnt0,ext;
	for(ri i=1;i<=m;++i){
		cnt0=calc(n,ans+'0');
		ll ext=ans!=""&&suf(n,ans,0,ans.size());
		if(k<=ext)break;
		if(k<=ext+cnt0)k-=ext,ans+='0';
		else k-=ext+cnt0,ans+='1';
	}
	cout<<ans;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值