Codeforces Round#543 div1 题意及题解

题目传送门:#543 div1


A. Diana and Liana

题目描述:

有一株很长的藤本植物,它的组成可以用一个长度为 m m m的正整数序列 a i a_i ai来表示,这株植物将被从下标为1的位置开始,每次切出长度为 k k k的一段,总共 n n n段(保证 m ≥ n ∗ k m\ge n*k mnk)。
Diana希望切出的某一段中包含她想要的数,用长度为 s s s的正整数序列 b i b_i bi来表示,如果数字 x x x b i b_i bi中出现了 y y y次,那么在切出的段中至少也要有 y y y次,对顺序没有要求(简单地说就是可重集包含关系)。为了达到这个目的,Diana可以删除植物中的一些位置,但是必须保证删除后长度仍然大于等于 n ∗ k n*k nk

输出一种可能的删除方案,如果没有,输出 − 1 -1 1
数据范围: 1 ≤ n , k , m ≤ 5 ∗ 1 0 5 , 1 ≤ s ≤ k 1\le n,k,m\le5*10^5,1\le s\le k 1n,k,m5105,1sk a i , b i ≤ 5 ∗ 1 0 5 a_i,b_i\le 5*10^5 ai,bi5105

题目分析:

对于 a a a序列中的一段区间 [ l , r ] [l,r] [l,r],可以得出它如果要被放到切出的某一段中,需要删除的个数是 r − ⌈ l k ⌉ ∗ k r-\lceil\frac lk\rceil*k rklk,所以很容易判断一段区间是否取得到。
那么从小到大枚举左端点 l l l,能够包含 b b b序列的最近的右端点 r r r是递增的,再判断一下能否压缩到切割的一段中,如果可以就可以得出答案了。

Code:

#include<bits/stdc++.h>
#define maxn 500005
using namespace std;
int m,n,k,s,a[maxn],b[maxn],nd[maxn],num,own,cnt[maxn];
bool vis[maxn];
void solve(int S){
	for(int i=S;;i++){
		if(nd[a[i]]>0) vis[i]=1;
		if(!--nd[a[i]]) num--;
		if(!num){
			int t=i-S+1-s-(m-n*k),j=S;
			while(t>0) {if(!vis[j]) vis[j]=1,t--;j++;}
			printf("%d\n",t<0?i-S+1-s:m-n*k);
			for(j=S;j<=i;j++) if(!vis[j]) printf("%d ",j);
			return;
		}
	}
}
int main()
{
	scanf("%d%d%d%d",&m,&k,&n,&s);
	for(int i=1;i<=m;i++) scanf("%d",&a[i]);
	for(int i=1;i<=s;i++){
		scanf("%d",&b[i]);
		if(!nd[b[i]]++) num++;
	}
	for(int i=1,j=1;i<=m;i++){
		while(j<=m&&own<num) {if(nd[a[j]]&&++cnt[a[j]]==nd[a[j]]) own++;j++;}
		if(own<num) break;
		if(j-(i+k-1)/k*k-1<=m-n*k) return solve(i),0;
		if(nd[a[i]]&&--cnt[a[i]]<nd[a[i]]) own--;
	}
	puts("-1");
}

写起来十分不顺。。


B. Once in a casino

题目描述:

长度为 n n n的数字串 a a a b b b(没有前导0),你需要把 a a a串经过如下操作转成 b b b串:

  • i       1 i~~~~~1 i     1 a i a_i ai a i + 1 a_{i+1} ai+1加上1
  • i    − 1 i~~-1 i  1 a i a_i ai a i + 1 a_{i+1} ai+1减去1

操作中 1 ≤ i ≤ n − 1 1\le i\le n-1 1in1,同时需要保证所有的 a i a_i ai在操作之后仍然在0~9范围内,首位不为0。
输出最小的操作次数 a n s ans ans,以及前 m i n ( a n s , 1 0 5 ) min(ans,10^5) min(ans,105)次操作。
如果不可能完成,输出-1
n ≤ 1 0 5 n\le 10^5 n105

题目分析:

如果可以把 a i a_i ai减成负数或者加到大于9,那么从左到右考虑很容易得出操作次数 a n s ans ans
同时如果答案不是-1,那么一定有可行的操作。
考虑实际从左到右操作时执行 i    1 i ~~1 i  1,但此时 a i + 1 a_{i+1} ai+1已经是9,由于最后的 a i + 1 a_{i+1} ai+1在0~9范围内,所以可以先执行 ( i + 1 )    − 1 (i+1)~~-1 (i+1)  1,(如果可以的话),而不会产生多余的代价。如果 a i + 2 a_{i+2} ai+2又不满足,可以一直这样递归下去,直到可行。由于每一次递归都代表着一次操作,所以可以直接暴力递归。

Code:

#include<cstdio>
#include<algorithm>
#define maxn 100005
using namespace std;
int n,a[maxn],b[maxn],op[maxn],cnt;
char A[maxn],B[maxn];
long long ans;
int main()
{
	scanf("%d%s%s",&n,A+1,B+1);
	for(int i=1;i<=n;i++) a[i]=A[i]-'0',b[i]=B[i]-'0';
	for(int i=1;i<n;i++) op[i]=b[i]-a[i],a[i+1]+=b[i]-a[i],ans+=abs(b[i]-a[i]);
	if(a[n]!=b[n]) return puts("-1"),0;
	else printf("%I64d\n",ans);
	for(int i=1;i<=n;i++) a[i]=A[i]-'0';
	for(int i=1,j;i<n;i++)
		while(op[i]){
			int d=op[i]>0?1:-1;
			for(j=i;a[j+1]+d<0||a[j+1]+d>9;j++,d=-d);
			for(;j>=i&&cnt<100000;j--,d=-d) a[j]+=d,a[j+1]+=d,op[j]-=d,printf("%d %d\n",j,d),cnt++;
			if(cnt==100000) return 0;
		}
}

C. Compress String

题目描述:

给出一个长度为 n n n的字符串 s s s以及两个数 a , b a,b a,b,你需要把字符串分成 s = t 1 t 2 . . . t k s=t_1t_2...t_k s=t1t2...tk,划分需要满足:

  • ∣ t i ∣ = 1 |t_i|=1 ti=1,可以花费 a a a个硬币划分出这一段。
  • t i t_i ti t 1 t 2 . . . t i − 1 t_1t_2...t_{i-1} t1t2...ti1的一个子串,可以花费 b b b个硬币划分出这一段。

求最小花费。 1 ≤ n , a , b ≤ 5000 1\le n,a,b\le5000 1n,a,b5000

题目分析:

DP, f [ i ] f[i] f[i]表示前 i i i个字符需要的最小花费,考虑 b b b的情况
首先 f [ i ] f[i] f[i]肯定是单调不递减的,因为如果[j+1,i]是[1,j]的子串,则[j+1,i-1]也一定是,所以 f [ i − 1 ] ≤ f [ i ] f[i-1]\le f[i] f[i1]f[i]
如果最后一个划分作为前面的子串的结尾是 j j j,把以 j j j结尾和以 i i i结尾的字符串从末尾开始的匹配长度记为 m x [ i ] [ j ] mx[i][j] mx[i][j],那么由 f [ i ] f[i] f[i]单调得出 m x [ i ] [ j ] mx[i][j] mx[i][j]越大越好。
如果 s [ i ] = = s [ j ] s[i]==s[j] s[i]==s[j],那么 m x [ i ] [ j ] = m x [ i − 1 ] [ j − 1 ] + 1 mx[i][j]=mx[i-1][j-1]+1 mx[i][j]=mx[i1][j1]+1,否则 m x [ i ] [ j ] = 0 mx[i][j]=0 mx[i][j]=0
f [ i ] = m i n ( f [ i ] , f [ i − m x [ i ] [ j ] ] + b ) f[i]=min(f[i],f[i-mx[i][j]]+b) f[i]=min(f[i],f[imx[i][j]]+b)
就是一个 O ( n 2 ) O(n^2) O(n2)DP了

PS:也可以用f[i]->f[i+k],用SAM判断[i+1,i+k]是否是[1,i]的子串。

Code:

#include<bits/stdc++.h>
#define maxn 5005
using namespace std;
int n,a,b,f[maxn],mx[maxn][maxn];
char s[maxn];
int main()
{
	scanf("%d%d%d%s",&n,&a,&b,s+1);
	f[1]=a;
	for(int i=2;i<=n;i++){
		f[i]=1e9;
		for(int j=1;j<i;j++){
			if(s[i]==s[j]) mx[i][j]=mx[i-1][j-1]+1;
			mx[i][j]=min(mx[i][j],i-j);
			f[i]=min(f[i],f[i-mx[i][j]]+b);
		}
		f[i]=min(f[i],f[i-1]+a);
	}
	printf("%d",f[n]);
}

D. Power Tree

题目描述:

一棵以1为根, n n n个节点的树,开始时每个叶子上有一个整数,控制节点 i i i需要花费 c i c_i ci,控制一个节点之后可以使得这个节点的子树整体加或减去任意一个数,求最小花费,使得不论最初叶子上的整数是什么,都可以让它变为0。同时需要输出所有可能被控制的点,即至少出现在一种最优方案中的点。
2 ≤ n ≤ 200000 , 0 ≤ c i ≤ 1 0 9 2\le n\le200000,0\le c_i\le 10^9 2n2000000ci109

题目分析:
法一:

子树对应的叶节点一定是一段连续的区间,那么问题相当于一段整数序列 a i a_i ai,需要知道多少区间的前缀和之差,才能够求出每个位置上的元素值。
把问题转化为图论,一个节点可控制的叶节点区间是 [ l , r ] [l,r] [l,r],那么连一条 ( l − 1 , r ) (l-1,r) (l1,r),权值为 c c c的边,
只要0~cnt所有的点都连通了,就满足条件了(cnt为叶节点个数),用Kruskal算法求最小生成树即可。
(然而这种方法求方案需要枚举每条边看能否替换,需要倍增求树上两点距离最大值。。)

Code:

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define maxn 200005
using namespace std;
int n,a[maxn],in[maxn],out[maxn],tim;
int fir[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],tot;
vector<int>vec;
struct node{
	int x,y,z;
	bool operator < (const node &p)const{return z<p.z;}
}e[maxn];
inline void line(int x,int y,int z=0){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
void dfs(int u,int pre){
	in[u]=tim;
	for(int i=fir[u];i;i=nxt[i]) if(to[i]!=pre) dfs(to[i],u);
	if((out[u]=tim)==in[u]) out[u]=++tim;
}
int F[maxn];
int find(int x){return x==F[x]?x:F[x]=find(F[x]);}
void Kruskal(){
	for(int i=0;i<=tim;i++) F[i]=i;
	sort(e+1,e+1+n);
	int cnt=0,x,y;
	long long ans=0;
	for(int i=1;i<=n&&cnt<tim;i++)
		if((x=find(e[i].x))!=(y=find(e[i].y))){
			F[x]=y,ans+=e[i].z,cnt++;
			line(e[i].x,e[i].y,e[i].z),line(e[i].y,e[i].x,e[i].z);
		}
	printf("%I64d",ans);
}
int f[maxn][20],g[maxn][20],dep[maxn];
void dfs2(int u,int pre){
	for(int i=fir[u];i;i=nxt[i]) if(to[i]!=pre)
		f[to[i]][0]=u,g[to[i]][0]=w[i],dep[to[i]]=dep[u]+1,dfs2(to[i],u);
}
int get(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	int d=dep[u]-dep[v],s=0;
	for(int i=0;i<=17;i++) if(d>>i&1) s=max(s,g[u][i]),u=f[u][i];
	if(u==v) return s;
	for(int i=17;i>=0;i--)
		if(f[u][i]!=f[v][i]) s=max(s,max(g[u][i],g[v][i])),u=f[u][i],v=f[v][i];
	return max(s,max(g[u][0],g[v][0]));
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x);
	dfs(1,0);
	for(int i=1;i<=n;i++) e[i]=(node){in[i],out[i],a[i]};
	memset(fir,0,sizeof fir),tot=0;
	Kruskal();
	memset(f,-1,sizeof f);
	dfs2(0,-1);
	for(int j=1;j<=17;j++)
		for(int i=0;i<=tim;i++)
			if(~f[i][j-1]) f[i][j]=f[f[i][j-1]][j-1],g[i][j]=max(g[i][j-1],g[f[i][j-1]][j-1]);
	for(int i=1;i<=n;i++) if(get(in[i],out[i])==a[i]) vec.push_back(i);
	printf(" %d\n",vec.size());
	for(int i=0;i<vec.size();i++) printf("%d ",vec[i]);
}

PS:注意long long

法二:

如果一个节点的儿子中有多个叶子,那么最多只有一个叶子节点不会被选择(可以通过它的父亲与其它点加减得到)。
那么我们用一个状态 f [ u ] [ 0 / 1 ] f[u][0/1] f[u][0/1],表示u这个点还需不需要它的祖先来帮忙覆盖它的某个叶子,它的值表示最小的控制代价,显然需要帮忙的叶子至多只能有1个。然后就有:
f [ u ] [ 1 ] = m i n v { f [ v ] [ 1 ] + ∑ w ≠ v f [ w ] [ 0 ] } f [ u ] [ 0 ] = m i n ( ∑ f [ v ] [ 0 ] , f [ u ] [ 1 ] + c [ u ] ) f[u][1]=min_v\{f[v][1]+\sum_{w\neq v} f[w][0]\}\\ f[u][0]=min(\sum f[v][0],f[u][1]+c[u]) f[u][1]=minv{f[v][1]+w̸=vf[w][0]}f[u][0]=min(f[v][0],f[u][1]+c[u])
最后 f [ 1 ] [ 0 ] f[1][0] f[1][0]就是最小花费,方案的话再根据DP值反推一遍就好了。

…code咕掉了


E. The very same Munchhausen

题目描述:

给出一个数 a a a ( 2 ≤ a ≤ 1 0 3 2\le a\le 10^3 2a103),求一个数 n n n使得 S ( a ∗ n ) ∗ a = S ( n ) S(a*n)*a=S(n) S(an)a=S(n),其中 S ( x ) S(x) S(x)表示 x x x的各位数字之和。
无解输出 − 1 -1 1
保证如果有解则存在 n n n的长度 ≤ 5 ∗ 1 0 5 \le 5*10^5 5105

题目分析:

一看就是个数学DP题。
不存在的法一:数学推导,这是我看过的最长cf题解。。
和蔼亲切的法二: DP
考虑从低位往高位填数,用 f [ x ] [ i ] [ j ] [ 0 / 1 ] f[x][i][j][0/1] f[x][i][j][0/1]表示填了 x x x
i i i表示前 x x x位构成的数 × a ×a ×a后会向 x + 1 x+1 x+1进多少位
j j j表示前 x x x × a ×a ×a的数位之和与前 x x x位的数位之和的差
0/1表示前 x x x位是否全为0.
f [ x ] [ i ] [ j ] [ 0 / 1 ] = 0 f[x][i][j][0/1]=0 f[x][i][j][0/1]=0表示这个状态不合法(取不到)
f [ x ] [ i ] [ j ] [ 0 / 1 ] = 1 f[x][i][j][0/1]=1 f[x][i][j][0/1]=1表示这个状态合法(取得到)

转移时可以省去第一维,用bfs枚举当前为0~9转移
答案状态就是 f [ 0 ] [ 0 ] [ 1 ] f[0][0][1] f[0][0][1],输出方案需要用 d i g [ i ] [ j ] [ 0 / 1 ] dig[i][j][0/1] dig[i][j][0/1]记录当前位,以及 p r e [ i ] [ j ] [ 0 / 1 ] pre[i][j][0/1] pre[i][j][0/1]三元组记录上一状态
因为 a ≤ 1000 a\le1000 a1000,所以 i ≤ 1000 i\le1000 i1000,而CF的题解上说可以去掉 ∣ j ∣ &gt; a |j|&gt;a j>a的状态,所以 − 1000 ≤ j ≤ 1000 -1000\le j\le1000 1000j1000.(然而为什么可以去掉并不知道。。欢迎dalao留言。。)
总的状态数就是1000*2000,bfs轻松跑过。

Code:

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
const int N = 4000;
struct node{
	int x,y,z;
}pre[1005][N+5][2];
int a,dg[1005][N+5][2];
bool vis[1005][N+5][2];
string solve(){
	const int K = 2000;
	queue<node>q;
	vis[0][K][0]=1,dg[0][K][0]=-1;
	q.push({0,K,0});
	while(!q.empty()){
		int i=q.front().x,j=q.front().y,flg=q.front().z;
		q.pop();
		if(!i&&j==K&&flg){
			string ans=""; node tmp;
			while(dg[i][j][flg]!=-1){
				if(!ans.empty()||dg[i][j][flg])
					ans+=(dg[i][j][flg]+'0');
				tmp=pre[i][j][flg];
				i=tmp.x,j=tmp.y,flg=tmp.z;
			}
			return ans;
		}
		for(int d=0;d<=9;d++){
			int x=(i+d*a)/10;
			int y=(i+d*a)%10*a-d+j;
			int z=(flg||d);
			if(y>=0&&y<=N&&!vis[x][y][z])
				vis[x][y][z]=1,dg[x][y][z]=d,pre[x][y][z]={i,j,flg},q.push({x,y,z});
		}
	}
	return "-1";
}
int main()
{
	scanf("%d",&a);
	cout<<solve()<<endl;
}

F. Secret Letters

题目描述:

n n n次事件,两个人W和P,每次事件用“ t i    p i t_i~~p_i ti  pi"表示,例如”"5 W"表示在 t = 5 t=5 t=5时W会送出一封信。送信的方式有两种:

  • 花费 d d d,将信直接送到另一个人手中。
  • 将信送到R先生处寄存,寄存的数量不限,但是另一个人要取信(可以一次取多封)就必须同时送一封信,否则就只能等到 t n + 1 t_{n+1} tn+1时刻取出所有的信。一封在 t a t_a ta时寄存、 t b t_b tb时取出的信的花费是 c ∗ ( t b − t a ) c*(t_b-t_a) c(tbta)

n ≤ 1 0 5 , c ≤ 1 0 2 , d ≤ 1 0 8 n\le 10^5,c\le10^2,d\le 10^8 n105,c102,d108

求所有事件送信的花费最小值。

题目分析:

会发现当信第一次( t i t_i ti)送到R先生处时,就一定会有一个 c ∗ ( t n − t i ) c*(t_n-t_i) c(tnti)的代价,因为要取信就必须放信,两人不论交替或不交替,都至少会有一封信一直留到 t n + 1 t_{n+1} tn+1,而且可以发现,能够交替的就交替比不交替优,不会比在后面再交替劣(因为交替不会产生多余的代价)。
枚举第一次送信到R先生处的时间 t i t_i ti,如果中途有多次同一人送信,就比较“ d d d”与“存放在R处直到下一次交替”两者的代价选优,能交替送R就交替(相当于可以省去一次代价)。可以发现对于第 k k k次事件的处理方式在第一封信已经送到R处的情况下是可以确定的,所以从大到小枚举 t i t_i ti,可以在 O ( n ) O(n) O(n)的时间内解决本题。

Code:

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
int n,c,d,t[maxn];
char op[maxn];
int main()
{
	scanf("%d%d%d",&n,&c,&d);
	for(int i=0;i<n;i++) scanf("%d %c",&t[i],&op[i]);
	scanf("%d",&t[n]),op[n]='?';
	long long ans=1ll*n*d,s=0;
	int last=t[n];
	for(int i=n-1;i>=0;i--){
		if(op[i]==op[i+1]) s+=min(1ll*d,1ll*(last-t[i+1])*c);
		else last=t[i+1];
		ans=min(ans,1ll*d*i+s+1ll*(t[n]-t[i])*c);
	}
	printf("%I64d\n",ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值