Educational Codeforces Round 53 (Rated for Div. 2)题解

1073A. Diverse Substring

传送门:
http://codeforces.com/contest/1073/problem/A
题目大意:
定义长度为n的字符串是一个多样化的字符串:当且仅当这个字符串没有一种字符数目严格大于n/2
找出字符串s的一个多样化子串
|s|<=1000
分析:
可以直接预处理前缀和数组,暴力 O ( ∣ s ∣ 2 σ ) O(|s|^2\sigma) O(s2σ)判断是否存在有没有一个多样化的子串

但这题有更简单的做法 考虑如何构造出多样化的子串
ab显然是一个多样化的子串 a不是多样化子串
因此只需要判断s是否有连续两个字符不同,如果有的话,这连续两个不同的字符就是一个多样化子串。如果s的字符全部相同,就不存在多样化子串

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
int n;
char s[1100];
int main(){
	scanf("%d%s",&n,s+1);
	fo(i,2,n) if (s[i]!=s[i-1]){
		printf("YES\n%c%c\n",s[i-1],s[i]);
		return 0;
	}
	printf("NO\n");
	return 0;
}"

1073B. Vasya and Books

传送门:
http://codeforces.com/contest/1073/problem/B
题目大意:
有n本编号为1~n的书放在一个栈a里
a[1]是栈顶 a[n]是栈底
然后按照b[1]…b[n]的顺序执行操作b[i]将书放进书包里
执行第i个操作时,假设b[i]=x 如果x还在栈a中 那么将a中x及之前所有的书都放进书包里 否则忽视这个操作
求出每一个操作放了多少本书
分析:
直接模拟 用v[x]表示编号为x的书是否还在栈中 维护+暴力处理即可

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
int n,a[330000],b[330000],ans[330000],now;
bool v[330000];
int main(){
	scanf("%d",&n);
	fo(i,1,n) scanf("%d",&a[i]);
	fo(i,1,n) scanf("%d",&b[i]);
	now=1;//!!!
	fo(i,1,n){
		if (v[b[i]]) continue;
		while (1){
			ans[i]++;
			v[a[now]]=1;
			now++;
			if (v[b[i]]) break;
		}
	}
	fo(i,1,n) printf("%d ",ans[i]);
	return 0;
}

1073C. Vasya and Robot

传送门:
http://codeforces.com/contest/1073/problem/C
题目大意:
给定一个长度为n的四方向序列 机器人会沿着这个序列走动 需要修改一些方向 使得机器人最终能够到达(x,y) 修改时只能将一个方向替换为另一个方向 不能添加/删除
判断是否有可能到达(x,y) 可能的话需要最小化(最后一个修改位置-最前一个修改位置+1)
n<=200000
分析:
显然 机器人最终到达的位置和序列中这些方向的顺序无关 只和不同方向的数量有关
如果需要修改操作最前面的位置在l 最后面的位置在r 那么修改掉[l,r]之间的所有方向 当前代价都不会变化(仍为r-l+1)
也就是说 可以用一个区间[l,r]代表一种决策
[l,r]之间的方向都任意选取 代价均为r-l+1
任意选取的话 机器人可能达到的区域最大 越有可能到达(x,y)
到达(x,y)的必要条件是 |n|与x+y奇偶性相同
因为从(0,0)开始每走一步 |x+y|奇偶性都会变化
如果|x+y|和n奇偶性不等的话 说明无解
显然 可以任意修改方向的区间长度越长(r-l+1越大) 那么修改后可能到达的区域越大 越可能到达(x,y)
因此可以考虑二分
二分判断的时候枚举任选方向的区间的起始位置i即可
如果任选区间是 [i,i+len-1]
在x+y与n奇偶性相同的情况下
[1,i-1]∪ [i+len,n]这些方向如果能够到达(x0,y0) 那么修改[i,i+len-1]的方向就能够到达(x,y)的充分必要条件为
abs(x+y-x0-y0)<=l
abs(x-x0)+abs(y-y0)<=l
这样时间复杂度是O(nlogn)

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
int n,x,y,L,R,mi,a[330000][4],x0,y0;
char s[330000];
inline void cal(int l1,int r1,int l2,int r2){
	x0=(a[r2][3]-a[l2-1][3]+a[r1][3]-a[l1-1][3])-(a[r2][2]-a[l2-1][2]+a[r1][2]-a[l1-1][2]);
	y0=(a[r2][0]-a[l2-1][0]+a[r1][0]-a[l1-1][0])-(a[r2][1]-a[l2-1][1]+a[r1][1]-a[l1-1][1]);
}
inline bool che(int l){
	fo(i,1,n-l+1){
		cal(1,i-1,i+l,n);
		if (abs(x-x0)+abs(y-y0)<=l) return 1;
	}
	return 0;
}
int main(){
	scanf("%d%s%d%d",&n,s+1,&x,&y);
	/*if (n&1!=abs(x+y)&1){
		printf("-1\n");
		return 0;
	}*/
	if ((n&1)!=(abs(x+y)&1)){//!!! 注意运算优先级
		printf("-1\n");
		return 0;
	}	
	fo(i,1,n){
		fo(j,0,3) a[i][j]=a[i-1][j];
		switch (s[i]){
			case 'U':a[i][0]++;break;// 要用break结束当前case 
			case 'D':a[i][1]++;break;// 要用break结束当前case 
			case 'L':a[i][2]++;break;// 要用break结束当前case 
			case 'R':a[i][3]++;break;
		}
		//fo(j,0,3) printf("%d %d:%d\n",i,j,a[i][j]); 
	}
	cal(1,1,2,n);
	//printf("%d %d\n",x0,y0);
	if (x0==x&&y0==y){
		printf("0\n");
		return 0;
	}
	L=1;R=n;
	while (L+2<R){
		mi=(L+R)>>1;
		if (che(mi)) R=mi-1;else L=mi+1;
	}
	fo(i,L,n) if (che(i)){
		printf("%d\n",i);
		return 0;
	}
	printf("-1\n");
	return 0;
}

1073D. Berland Fair

传送门:
http://codeforces.com/contest/1073/problem/D
题目大意:
有n个物品 每个物品价值为a[i]
一个人最开始金钱为T 按1,2,3…n,1,2,3…n,1,2…n的顺序买东西 如果可以买下当前物品就会立即购买 然后考虑下一个物品
求出最终会买下多少物品
分析:
考虑到所有购买到的物品的序列是存在循环节的
考虑先模拟一次 从1~n走一圈 能买就尽量买
然后统计这一圈购买的物品价值和sum 以及数目num
如果num=0 那么说明再继续考虑下去也还是买不到物品 循环终止
如果num>0 那么再购买第二圈 第三圈… 因为拥有的金钱不会增加 可能购买到的物品一定上第一圈购买物品的子集
事实上 如果走完第一圈后剩下的金钱等于T’=Ksum+rem
那么之后考虑购买K圈物品,买到的物品都与第一圈相同
那么就不需要模拟这k圈的过程
可以直接让T’’=rem ans+=num
K
然后T’’<sum 这个时候每走一圈购买物品的集合与之前发生了变化
这个时候就暴力模拟 重新走一圈 计算新的sum’和num’
然后再采用同样的构成处理下去…

每次暴力重新计算sum和num的复杂度都是O(n)的
如果需要暴力重新计算很多次的话 会TLE
但是实际上并不会TLE
这是因为这题的数值范围是有限的
T < = 1 0 18 T<=10^{18} T<=1018
a i < = 1 0 9 a_{i}<=10^{9} ai<=109
考虑如何构造一个序列a 以及金钱T
使得暴力重新计算的次数尽可能多
可以让
a = 1 , 2 , 4 , 8 , 16 , 32 , 64 , 128...... a={1,2,4,8,16,32,64,128......} a=1,2,4,8,16,32,64,128......
T = 1 + ( 1 + 2 ) + ( 1 + 2 + 4 ) + ( 1 + 2 + 4 + 8 ) . . . . . . T=1+(1+2)+(1+2+4)+(1+2+4+8)...... T=1+(1+2)+(1+2+4)+(1+2+4+8)......
这样的话重新计算sum和num次数是很大的 是O(n)的
这样总时间复杂度会达到 O ( n 2 ) O(n^2) O(n2)
但这样构造数据的话 a i a_{i} ai T T T增长是非常快的 可以计算出达到题目中的数值范围时 n最多只能达到30左右
因此总时间复杂度可以看成是 O ( k n ) O(kn) O(kn)
无脑暴力做即可

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
int n;
long long T,sum,num,ans,a[300000];
int main(){
    scanf("%d%lld",&n,&T);
    fo(i,1,n) scanf("%lld",&a[i]);
    while (T){
        sum=0;num=0;
        fo(i,1,n) if (T>=a[i]){
            T-=a[i];
            sum+=a[i];
            ans++;
            num++;
        }
        if (!num) break;//!!!
        ans+=T/sum*num;
        T%=sum;
    }
    printf("%lld\n",ans);
    return 0;
}

1073E. Segment Sum

传送门:
http://codeforces.com/contest/1073/problem/E
题目大意:
求出[l,r]中所有出现数位种类数不超过k的数之和
1 ≤ l ≤ r < 1 0 18 , 1 ≤ k ≤ 10 1 \le l \le r < 10^{18}, 1 \le k \le 10 1lr<1018,1k10
分析:
数位dp经典题
将[l,r]的计算转化为[1,r]-[1,l-1]的答案计算
假设现在求解的问题是1~n中所有数位种类数不超过k的数之和

f[i][j][0/1/2]表示数位长度为i ‘0’~‘9’是否出现的二进制状态为j比n的前i位小/相等/大的数的个数

g[i][j][0/1/2]表示数位长度为i ‘0’~‘9’是否出现的二进制状态为j比n的前i位小/相等/大的所有数的和(对998244353取模后的和)

然后有状态转移方程
f [ i + 1 ] [ j ′ ] [ c ′ ] + = f [ i ] [ j ] [ c ] f[i+1][j'][c']+=f[i][j][c] f[i+1][j][c]+=f[i][j][c]
g [ i + 1 ] [ j ′ ] [ c ′ ] + = ( g [ i ] [ j ] [ c ] ∗ 10 + f [ i ] [ j ] [ c ] ∗ l ) , l = 0..9 g[i+1][j'][c']+=(g[i][j][c]*10+f[i][j][c]*l) ,l=0..9 g[i+1][j][c]+=(g[i][j][c]10+f[i][j][c]l),l=0..9
时间复杂度为 O ( l o g 10 r ∗ 2 k ∗ 30 ) O(log_{10}{r}*2^k*30) O(log10r2k30)
时间复杂度还可以优化到更小一些
题目加强到求平方和也可以类似dp求解

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
const int p=998244353;
long long l,r,ans;
int k,d[20],dt,bn[1200],nn,tt;
long long f[20][1200][3],g[20][1200][3];
inline void cal(long long n,int ww){
    long long sum=0;int cc,jj;
    dt=0;
    while (n){
        d[++dt]=n%10;
        n/=10;
    }
    fo(i,1,dt/2) std::swap(d[i],d[dt-i+1]);
    fo(i,1,dt) fo(j,0,1023) fo(c,0,2) f[i][j][c]=g[i][j][c]=0;
    fo(l,1,9){
        if (l<d[1]) cc=0;
        if (l>d[1]) cc=2;
        if (l==d[1]) cc=1;
        f[1][1<<l][cc]=1;g[1][1<<l][cc]=l;
    }
    fo(i,1,dt-1)
        fo(j,0,1023)
            if (bn[j]<=k)
                fo(c,0,2)
                    fo(l,0,9){
                        cc=c;
                        if (cc==1){
                            if (l<d[i+1]) cc=0;
                            if (l>d[i+1]) cc=2;
                        }
                        jj=j|(1<<l);
                        f[i+1][jj][cc]+=f[i][j][c];
                        if (f[i+1][jj][cc]>=p) f[i+1][jj][cc]-=p;
                        g[i+1][jj][cc]=(g[i+1][jj][cc]+g[i][j][c]*10+f[i][j][c]*l)%p;                   
                    }
    fo(i,1,dt)
        fo(j,0,1023)
            if (bn[j]<=k)
                fo(c,0,2){
                    if (c==2&&i==dt) continue;
                    sum+=g[i][j][c];
                    if (sum>=p) sum-=p;
                }
    ans+=ww*sum;
}
int main(){
    fo(i,0,1023){
        nn=i;tt=0;
        while (nn){
            tt++;
            nn-=nn&(-nn);
        }
        bn[i]=tt;
        //printf("%d:%d\n",i,tt);
    }
    scanf("%I64d%I64d%d",&l,&r,&k);
    cal(r,1);
    cal(l-1,-1);
    ans=(ans%p+p)%p;
    printf("%I64d\n",ans);
    return 0;
}

1073F. Choosing Two Paths

待补题

1073G. Yet Another LCP Problem

待补题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值