T1 命令方块(block)
题意
有 N N N 个字符串 s i s_i si,按给定顺序排开,你每次可以交换一对字符串,最后使得:
- 对于任意任意 i < j < k i<j<k i<j<k,有: lcp ( s i , s j ) ≥ lcp ( s i , s k ) \operatorname{lcp}(s_i,s_j)\geq \operatorname{lcp}(s_i,s_k) lcp(si,sj)≥lcp(si,sk) 且 lcp ( s j , s k ) ≥ lcp ( s i , s k ) \operatorname{lcp}(s_j,s_k)\geq \operatorname{lcp}(s_i,s_k) lcp(sj,sk)≥lcp(si,sk)
输出任意一种交换方案,要求交换次数 ≤ 1 0 6 \leq 10^6 ≤106。 N ≤ 1 0 6 , ∑ ∣ s i ∣ ≤ 1 0 7 N\leq 10^6, \sum |s_i|\leq 10^7 N≤106,∑∣si∣≤107。
题解
(刚看到题目名称还有 SPJ 还以为第一道题就是造计算机题……)
首先把要求翻译成人话——对于任意一个字符串,向同一个方向看,离得越近的最长公共前缀越长。
最长公共前缀越长就相当于字典序更接近,所以按字典序 sort
一遍就行。
(实际上按字典树的任意 DFS 序排序都是可行的)
得到了排序后的序列,只需要对于每个没有归位的字符串 swap
一遍,就能得到方案。
代码:
#include<bits/stdc++.h>
using namespace std;
int getint(){
int ans=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
char c[10001000];
pair<string,int> s[1000100];
int pos[1000100],a[1000100];
pair<int,int>ans[1000100];
int cnt=0;
int main(){
int n=getint();
for(int i=0;i<n;i++){
scanf("%s",c);
s[i].first=c;
s[i].second=i;
}
sort(s,s+n);
for(int i=0;i<n;i++){
pos[i]=i;
a[i]=i;
}
for(int i=0;i<n;i++){
if(s[i].second!=a[i]){
int t=pos[s[i].second];
ans[cnt]=make_pair(i+1,t+1);
++cnt;
swap(a[i],a[t]);
pos[a[i]]=i;
pos[a[t]]=t;
}
}
printf("%d\n",cnt);
for(int i=0;i<cnt;i++){
printf("%d %d\n",ans[i].first,ans[i].second);
}
return 0;
}
T2 骨粉(bone)
题意
有 n n n 个数 t i t_i ti,每一秒你可以将任意一个数减少 x x x,每一秒每一个数都会自然减少 1。求 s s s 秒后最大数的最小值。有多次询问。 n , m ≤ 1 0 5 , s i , x , ∑ ∣ t i ∣ ≤ 1 0 18 n,m\leq 10^5,\;s_i,x,\sum |t_i|\leq 10^{18} n,m≤105,si,x,∑∣ti∣≤1018
题解
首先目测可以二分答案。于是每次 check O ( n ) O(n) O(n) 扫一遍,统计 ∑ max ( ⌈ t i − v x ⌉ , 0 ) \sum \max(\lceil \dfrac{t_i-v}{x}\rceil,0) ∑max(⌈xti−v⌉,0)。60 分到手。
据说有一个很难想到但又并不偏门的套路:
⌈
t
i
−
v
x
⌉
=
⌈
(
⌊
t
i
x
⌋
+
{
t
i
x
}
)
−
(
⌊
v
x
⌋
+
{
v
x
}
)
⌉
=
⌈
(
⌊
t
i
x
⌋
−
⌊
v
x
⌋
)
+
(
{
t
i
x
}
−
{
v
x
}
)
⌉
=
⌊
t
i
x
⌋
−
⌊
v
x
⌋
+
⌈
{
t
i
x
}
−
{
v
x
}
⌉
=
⌊
t
i
x
⌋
−
⌊
v
x
⌋
+
[
t
i
m
o
d
x
>
v
m
o
d
x
]
\begin{aligned} &\left\lceil \dfrac{t_i-v}{x}\right\rceil\\ =&\left\lceil \left(\left\lfloor\dfrac{t_i}{x}\right\rfloor+\left\{\dfrac{t_i}{x}\right\}\right)-\left(\left\lfloor\dfrac{v}{x}\right\rfloor+\left\{\dfrac{v}{x}\right\}\right)\right\rceil\\ =&\left\lceil \left(\left\lfloor\dfrac{t_i}{x}\right\rfloor-\left\lfloor\dfrac{v}{x}\right\rfloor\right)+\left(\left\{\dfrac{t_i}{x}\right\}-\left\{\dfrac{v}{x}\right\}\right)\right\rceil\\ =&\lfloor\dfrac{t_i}{x}\rfloor-\lfloor\dfrac{v}{x}\rfloor+\left\lceil\{\dfrac{t_i}{x}\}-\{\dfrac{v}{x}\}\right\rceil\\ =&\lfloor\dfrac{t_i}{x}\rfloor-\lfloor\dfrac{v}{x}\rfloor+[t_i\bmod x>v\bmod x] \end{aligned}
====⌈xti−v⌉⌈(⌊xti⌋+{xti})−(⌊xv⌋+{xv})⌉⌈(⌊xti⌋−⌊xv⌋)+({xti}−{xv})⌉⌊xti⌋−⌊xv⌋+⌈{xti}−{xv}⌉⌊xti⌋−⌊xv⌋+[timodx>vmodx]
排序
t
i
t_i
ti 之后,二分找到
t
i
>
v
t_i>v
ti>v 的区间。
⌊
t
i
x
⌋
\lfloor\dfrac{t_i}{x}\rfloor
⌊xti⌋ 的区间和很容易维护,
⌊
v
x
⌋
\lfloor\dfrac{v}{x}\rfloor
⌊xv⌋ 的和直接计算,所以要着重考虑的是统计区间内
t
i
m
o
d
x
>
v
m
o
d
x
t_i\bmod x>v\bmod x
timodx>vmodx 的个数。可以通过离线维护。也可以用可持久化数据结构,把
t
i
m
o
d
x
t_i\bmod x
timodx 扔进主席树即可。
O ( log n + m log 2 n ) O(\log n+m\log^2n) O(logn+mlog2n)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll getint(){
ll ans=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
const int N=1e5+10;
int n,m;
ll x;
ll a[N];
double sum[N];
ll maxa;
int sz[N*62],ch[N*62][2],root[N],tot=0;
void add(ll x,int num){
++tot;
root[num]=tot;
int p=root[num],q=root[num-1];
for(int i=60;i>=0;--i){
int t=(x>>i)&1;
ch[p][t^1]=ch[q][t^1];
sz[p]=sz[q]+1;
++tot;
ch[p][t]=tot;
p=ch[p][t],q=ch[q][t];
}
sz[p]=sz[q]+1;
}
int query(int r,ll x){
//[1,r]>x
r=root[r];
int ans=0;
for(int i=60;i>=0;--i){
int t=(x>>i)&1;
if(t==0)ans+=sz[ch[r][1]];
r=ch[r][t];
}
return ans;
}
bool check(ll mid,ll s){
int p=lower_bound(a+1,a+n+1,mid,greater<long long>())-a-1;
double cnt=sum[p]-(mid/x)*1.0*p+query(p,mid%x);
assert(sum[p]>=(mid/x)*1.0*p);
//cerr<<"check "<<mid<<" "<<s<<" | "<<cnt<<" "<<p<<endl;
return cnt<=s+1e-10;
}
ll getans(ll s){
ll l=0,r=maxa,ans=0;
while(l<=r){
ll mid=l+r>>1;
//cerr<<"> "<<l<<" "<<r<<" "<<mid<<endl;
if(check(mid,s))ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
void putint(ll x){
if(x>=10)putint(x/10);
putchar('0'+x%10);
}
int main(){
n=getint(),m=getint(),x=getint();
for(int i=1;i<=n;i++){
a[i]=getint();
maxa=max(maxa,a[i]);
}
sort(a+1,a+n+1,greater<long long>());
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+a[i]/x;
assert(sum[i]>=0);
add(a[i]%x,i);
}
for(int i=0;i<m;i++){
ll s=getint();
ll ans=max(0ll,getans(s)-s);
//printf("%lld\n",ans);
putint(ans);putchar('\n');
}
return 0;
}
T3 字符串问题(string)
题意
有一个长为 n n n 的数字串,在其中加 k k k 个加号(允许出现前导零,不允许 1 + + 23 1++23 1++23 或 + 1 + 23 +1+23 +1+23 等格式),求所有合法表达式运算结果之和 m o d 998244353 \bmod 998244353 mod998244353。 1 ≤ n ≤ 5 ⋅ 1 0 5 , 0 ≤ k < n 1\leq n\leq 5\cdot10^5,0\leq k<n 1≤n≤5⋅105,0≤k<n
题解
对于每个数字单独计算它的贡献。它的贡献由它往右最近一个加号决定。(右侧没有加号特殊处理)
为了方便,数码从右往左从 0 到 n − 1 n-1 n−1 编号。
假设第 i i i 个数码往右 j j j 个数才会遇到一个 + + +,答案分两部分:
- i = j i=j i=j(即之后没有加号): ∑ i = 0 n − 1 a i × 1 0 i × ( n − i − 1 k ) \sum\limits_{i=0}^{n-1}a_i\times 10^i\times {n-i-1\choose k} i=0∑n−1ai×10i×(kn−i−1)
-
i
≠
j
i\ne j
i=j(即之后有加号):
∑ i = 0 n − 1 ∑ j = 0 i − 1 a i × 1 0 j × ( n − j − 2 k ) = ∑ i = 0 n − 1 a i ∑ j = 0 i − 1 1 0 j × ( n − j − 2 k ) = ∑ j = 0 n − 1 1 0 j × ( n − j − 2 k − 1 ) ∑ i = j + 1 n − 1 a i \begin{aligned} &\sum\limits_{i=0}^{n-1}\sum\limits_{j=0}^{i-1}a_i\times10^j\times{n-j-2\choose k}\\ =&\sum\limits_{i=0}^{n-1}a_i\sum\limits_{j=0}^{i-1}10^j\times{n-j-2\choose k}\\ =&\sum\limits_{j=0}^{n-1}10^j\times{n-j-2\choose k-1}\sum\limits_{i=j+1}^{n-1}a_i \end{aligned} ==i=0∑n−1j=0∑i−1ai×10j×(kn−j−2)i=0∑n−1aij=0∑i−110j×(kn−j−2)j=0∑n−110j×(k−1n−j−2)i=j+1∑n−1ai
预处理 10 的次幂、阶乘、阶乘的逆元、 a i a_i ai 的后缀和(按照原题的顺序是前缀和)就能 O ( n ) O(n) O(n) 求出答案。
代码(pow10
做变量名似乎会炸):
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll getint(){
ll ans=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
const int N=5e5+10,mod=998244353;
int pooooooooow10[N],fac[N],ifac[N],sum[N];
int qpow(int x,int y){
int ans=1;
while(y){
if(y&1)ans=ans*1ll*x%mod;
x=x*1ll*x%mod;
y>>=1;
}
return ans;
}
int getinv(int x){return qpow(x,mod-2);}
int c(int x,int y){
if(x<0||y<0||y>x)return 0;
return fac[x]*1ll*ifac[y]%mod*ifac[x-y]%mod;
}
int a[N];
int main(){
int n=getint(),k=getint();
for(int i=0;i<n;i++){
char c=getchar();
while(c<'0'||c>'9')c=getchar();
a[i]=c-'0';
if(i!=0)sum[i]=sum[i-1]+a[i];
else sum[i]=a[i];
}
pooooooooow10[0]=fac[0]=ifac[0]=1;
for(int i=1;i<=n;i++){
pooooooooow10[i]=pooooooooow10[i-1]*10ll%mod;
fac[i]=fac[i-1]*1ll*i%mod;
}
ifac[n]=getinv(fac[n]);
for(int i=n-1;i>=0;--i){
ifac[i]=ifac[i+1]*(i+1ll)%mod;
}
int ans=0;
for(int i=0;i<=n-1;i++){
ans=(ans+a[n-i-1]*1ll*pooooooooow10[i]%mod*c(n-i-1,k)%mod)%mod;
if(n-i-2>=0)ans=(ans+sum[n-i-2]*1ll*pooooooooow10[i]%mod*c(n-i-2,k-1)%mod)%mod;
}
cout<<ans;
return 0;
}