2019-11-12CSP-S模拟测

这次考炸了,T1其实冷静画画图就可以看出来的,为什么没有看出来呢?
每次都妄想能靠脑子直接看出答案,极其不会利用草稿纸,一定要好好反思,尝试着用各种角度看问题,各种方法在草稿纸上乱玩样例,这样才能够灵感乍现啊
妄图直接靠眼睛看出答案我未免也太高估自己智商了hh

T1

在这里插入图片描述
注意到题目中给定的性质:按照给定的字符串建造一棵 T r i e Trie Trie树,然后按照任意一种 d f s dfs dfs序输出,当前序列一定成立。
(对上文的感性解释:按照 T r i e Trie Trie树的 d f s dfs dfs序输出,一定是先把公共前缀走完再输出分别的序列,先走完的与后面构成的前缀长度一定 ≤ \le 后走的与后面构成的前缀长度)
然后继续对此性质进行研究:发现一个更为有趣的性质——直接将字符串排序即可满足题目要求
于是: s t r i n g string string读入,记录原来编号, s o r t sort sort排序,排序后 f o r for for循环交换,记录交换后编号即可
string和结构体真是慢到令人窒息

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
pair<int,int> sw[N];
struct node{int id;string ch;}per[N];
int n,cnt=0,rk[N];
bool cmp(node a,node b){return a.ch<b.ch;}
int main(){
	cin>>n;for(int i=1;i<=n;i++){cin>>per[i].ch;per[i].id=i;}
	sort(per+1,per+1+n,cmp);
	for(int i=1;i<=n;i++){rk[per[i].id]=i;}
	for(int i=1;i<=n;i++){if(per[i].id!=i){sw[++cnt]=make_pair(per[i].id,i);swap(rk[i],rk[per[i].id]);swap(per[i].id,per[rk[per[i].id]].id);}}
	printf("%d\n",cnt);for(int i=1;i<=cnt;i++){printf("%d %d\n",sw[i].first,sw[i].second);}
	return 0;
}

T2(可持久化线段树)

在这里插入图片描述
在这里插入图片描述
思路:二分答案,二分当前可达的“最大的最小 t i t_i ti"为 v v v,列出当前为达到此二分状态所需要的 s s s值,可以后缀和维护 t i t_i ti计算出需要的 s s s值,将 v v v提出来计算,用可持久化线段树维护当前在 % x \%x %x的意义下有多少个 t i t_i ti严格大于 v v v即可
t i t_i ti递增排序,维护后缀和,维护二分后有多少个 t i t_i ti严格大于 v v v
注:因为是后缀和,所以计算的都是 ∑ \sum 起来的值

T3(计数类问题)

CF520E Pluses everywhere
在这里插入图片描述
注意到根据加号放置位置与数的关系
讨论加号的位置(一共有 n − 1 n-1 n1个位置可以放置加号)

  1. 这个数在序列中段
    考虑在这个数两端添上加号,此时还剩 k − 2 k-2 k2个加号可以填,还剩可以填的位置有 n − l e n − 2 n-len-2 nlen2 ( n − 1 − ( l e n − 1 ) − 2 ) (n-1-(len-1)-2) n1(len1)2),其中 l e n − 1 len-1 len1为当前数长度占了的可以填加号的位置, n − 1 n-1 n1为本来可以填的位置, − 2 -2 2为本身钦定要填的加号。
  2. 这个数在序列头
    强制在数的末尾添上一个加号,还剩可填加号为 ( k − 1 ) (k-1) (k1),可填位置为 ( n − 2 ) (n-2) (n2),被占用位置为 ( r − 1 ) (r-1) (r1)
  3. 这个数在序列尾
    强制在这个数之前填上一个加号,还剩可填加号为 ( k − 1 ) (k-1) (k1),可填位置为 ( l − 1 ) (l-1) l1),被占用位置为 1 1 1

强制钦定当前加号位置后,考虑所填加号位置对答案的大小的影响:

  1. 在序列中段
    i i i位数,在当前数中作为第 j j j个位置出现:
    a i a_i ai后第1个位置,此时数对答案的贡献为 当 前 数 ∗ 1 0 0 ∗ 可 填 加 号 位 置 的 方 案 数 当前数*10^0*可填加号位置的方案数 100;
    a i a_i ai后第2个位置,此时数对答案的贡献为 当 前 数 ∗ 1 0 1 ∗ 可 填 加 号 位 置 的 方 案 数 当前数*10^1*可填加号位置的方案数 101;
    a i a_i ai后第3个位置,此时数对答案的贡献为 当 前 数 ∗ 1 0 2 ∗ 可 填 加 号 位 置 的 方 案 数 当前数*10^2*可填加号位置的方案数 102;
    … … ……
    表示出来为 f i ∗ 1 0 j − 1 ∗ C n − l e n − 2 k − 2 f_i*10^{j-1}*C^{k-2}_{n-len-2} fi10j1Cnlen2k2
  2. 在序列首
    f i ∗ 1 0 j ∗ C n − r − 1 k − 1 f_i*10^j*C^{k-1}_{n-r-1} fi10jCnr1k1
  3. 在序列末
    f i ∗ 1 0 j ∗ C l − 2 k − 1 f_i*10^j*C^{k-1}_{l-2} fi10jCl2k1

f i f_i fi为当前方案下所有长度为 i i i的数的大小, s u m sum sum预处理前缀和, v a l val val为单个数字的后缀和贡献, c a l c calc calc:相当于求出来当前 [ l , r ] [l,r] [l,r]之间的和
预处理出当前数 f i f_i fi是多少
假设当前序列为 a b c d e abcde abcde
i = = 1 i==1 i==1 f i = a + b + c + d + e f_i=a+b+c+d+e fi=a+b+c+d+e
i = = 2 i==2 i==2 f i = a b + b c + c d + d e = 10 ∗ ( a + b + c + d ) + ( b + c + d + e ) f_i=ab+bc+cd+de=10*(a+b+c+d)+(b+c+d+e) fi=ab+bc+cd+de=10(a+b+c+d)+(b+c+d+e)
i = = 3 i==3 i==3 f i = a b c + b c d + c d e = 10 ∗ ( a b + b c + c d ) + ( c + d + e ) f_i=abc+bcd+cde=10*(ab+bc+cd)+(c+d+e) fi=abc+bcd+cde=10(ab+bc+cd)+(c+d+e)
f i − 1 ∗ 10 + 当 前 长 度 的 后 缀 和 f_{i-1}*10+当前长度的后缀和 fi110+

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
const int Mod=998244353;
inline int read(){
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int fac[N],inv[N],pw[N],sum[N],n;char s[N];
int add(int a,int b){return ((a+b)>=Mod)?(a+b-Mod):(a+b);}
int mul(int a,int b){return 1ll*a%Mod*b%Mod;}
int ksm(int a,int b){int ans=1;for(;b;b>>=1){if(b&1)ans=mul(ans,a);a=mul(a,a);}return ans;}
void prework(){
	fac[0]=fac[1]=1;for(int i=2;i<=n;i++){fac[i]=mul(fac[i-1],i);}inv[0]=inv[1]=1;
	inv[n]=ksm(fac[n],Mod-2); for(int i=n-1;i>=2;i--){inv[i]=mul(inv[i+1],(i+1));}
	pw[0]=1;for(int i=1;i<=n;i++)pw[i]=mul(pw[i-1],10);
}
int calc(int l,int r){return add(sum[r],Mod-mul(sum[l-1],pw[r-l+1]));}
int C(int n,int m){if(n<m||n<0||m<0) return 0;return mul(fac[n],mul(inv[n-m],inv[m]));}
int val[N],f[N],k;
int main(){
	n=read(),k=read();scanf("%s",s+1);prework();
	for(int i=1;i<=n;i++)sum[i]=add(mul(sum[i-1],10),(s[i]-'0'));
	for(int i=n-1;i>=2;i--)val[i]=add(val[i+1],(s[i]-'0'));
	f[1]=val[2];
	for(int i=2;i<=n-2;i++){
		f[i]=mul(10,add(f[i-1],Mod-calc(n-i+1,n-1)));
		f[i]=add(f[i],val[i+1]);
	}
	int ans=0;
	for(int i=1;i<=n-2;i++){ans=add(ans,mul(f[i],C(n-i-2,k-2)));}//!!!!! 
	for(int r=1;r<=n;r++){ans=add(ans,mul(calc(1,r),C(n-r-1,k-1)));}
	for(int l=2;l<=n;l++){ans=add(ans,mul(calc(l,n),C(l-2,k-1)));}
	printf("%d",ans);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值