T1:
题目大意:
将 n n n个字符串排序,使得任意 i < j < k i < j < k i<j<k 满足: l c p ( s i , s k ) ≤ l c p ( s i , s j ) lcp(si, sk) ≤ lcp(si, sj ) lcp(si,sk)≤lcp(si,sj) 且 l c p ( s i , s k ) ≤ l c p ( s j , s k ) lcp(si, sk) ≤ lcp(sj , sk) lcp(si,sk)≤lcp(sj,sk)。 n ≤ 1 0 6 n\le10^6 n≤106
l c p : 最 长 公 共 前 缀 lcp:最长公共前缀 lcp:最长公共前缀
要求输出排序过程 (即每次交换哪两个字符串)。
解析:
构建出一棵字典树,那么以该树的任意一个dfs序输出都是一个合法的解。
然后我们只考虑一种简单的情况,即按照字典序输出,string类的sort可以方便完成这一任务。
考虑排序后输出一种合法的过程,那么我们可以对排序后的数列进行一次 O ( n ) O(n) O(n)的还原,输出过程即可。
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
Code
#include<bits/stdc++.h>
using namespace std;
namespace IO{
inline int Read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
void Get(){
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
}
}
#define Read() IO::Read()
struct Node{
string s;
int id;
}a[1000005];
bool cmp(Node x,Node y){
return x.s<y.s;
}
int m=0;
struct node{
int ans1,ans2;
}f[1000005];
signed main(){
IO::Get();
int n=Read();
for(register int i=1;i<=n;++i){
cin>>a[i].s;
a[i].id=i;
}
sort(a+1,a+n+1,cmp);
for(register int i=1;i<=n;++i){
while(a[i].id!=i){
++m;
f[m].ans1=a[i].id,f[m].ans2=a[a[i].id].id;
swap(a[i],a[a[i].id]);
}
}
printf("%d\n",m);
for(register int i=1;i<=m;++i){
printf("%d %d\n",f[i].ans1,f[i].ans2);
}
return 0;
}
T2:
题目大意:
有 n n n 个数 t i t_i ti,每一秒你可以将任意一个数减少 x x x,每一秒每一个数都会自然减少 1 1 1。求 s s s 秒后最大数的最小值。有多次询问。 n ≤ 1 0 5 n\le10^5 n≤105
解析:
我们要使得最大数最小,那么首先我们每秒减少的那个数必定是当前的最大数。
先忽略每个数的自然减少,在最后统计时加上即可。
考虑统计时二分答案,对于分出的一个最大数的最小值 v v v(忽略自然减少),我们有每一个数所需的减少 x x x的次数: m a x ( ⌈ ( t i − v ) / x ⌉ , 0 ) max(\lceil (t_i-v)/x\rceil,0) max(⌈(ti−v)/x⌉,0)
但这样做时间复杂度为 O ( n 2 ) O(n^2) O(n2),需要想办法优化。
还是从上面的式子开始:
⌈
(
t
i
−
v
)
/
x
⌉
=
⌈
(
⌊
t
i
x
⌋
+
{
t
i
x
}
)
−
(
⌊
v
x
⌋
+
{
v
x
}
)
⌉
\lceil (t_i-v)/x\rceil=\lceil(\lfloor\frac{t_i}{x}\rfloor+\{\frac{t_i}{x}\})-(\lfloor\frac{v}{x}\rfloor+\{\frac{v}{x}\})\rceil
⌈(ti−v)/x⌉=⌈(⌊xti⌋+{xti})−(⌊xv⌋+{xv})⌉
=
⌈
⌊
t
i
x
⌋
−
⌊
v
x
⌋
⌉
+
⌈
{
t
i
x
}
−
{
v
x
}
⌉
\qquad\qquad\space\space\space\space=\lceil\lfloor\frac{t_i}{x}\rfloor-\lfloor\frac{v}{x}\rfloor\rceil+\lceil\{\frac{t_i}{x}\}-\{\frac{v}{x}\}\rceil
=⌈⌊xti⌋−⌊xv⌋⌉+⌈{xti}−{xv}⌉
=
⌈
⌊
t
i
x
⌋
−
⌊
v
x
⌋
⌉
+
[
t
i
%
x
>
v
%
x
]
\qquad\qquad\space\space\space\space=\lceil\lfloor\frac{t_i}{x}\rfloor-\lfloor\frac{v}{x}\rfloor\rceil+[t_i\%x>v\%x]
=⌈⌊xti⌋−⌊xv⌋⌉+[ti%x>v%x]
前面的那部分可以预处理一个后缀和,后面部分可以离线用线段树做,也可以在线用主席树做。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,x,val[100005],sum[100005],t[100005],cnt;
namespace Tree{
struct node{
int lc,rc;
int x;
}seg[7500005];
void pushup(int o){
seg[o].x=seg[seg[o].lc].x+seg[seg[o].rc].x;
}
void modify(int &a,int l,int r,int pos,int v){
seg[++cnt]=seg[a];
a=cnt;
if(l==r){
seg[a].x+=v;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) modify(seg[a].lc,l,mid,pos,v);
if(mid<pos) modify(seg[a].rc,mid+1,r,pos,v);
pushup(a);
}
int query(int a,int l,int r,int nl,int nr){
if(!a) return 0;
if(nl<=l&&r<=nr) return seg[a].x;
if(nl>r||nr<l) return 0;
int mid=(l+r)>>1;
return query(seg[a].lc,l,mid,nl,nr)+query(seg[a].rc,mid+1,r,nl,nr);
}
}
int check(int y){
int res=0;
int p=lower_bound(val+1,val+n+1,y)-val;
res=sum[p]-(y/x)*(n-p+1);
res+=Tree::query(t[p],0,x,y%x+1,x);
return res;
}
int solve(int y){
int l=1,r=val[n];
while(l<r){
int mid=(l+r)>>1;
if(check(mid)>y) l=mid+1;
else r=mid;
}
return max(l-y,0ll);
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&x);
for(int i=1;i<=n;i++) scanf("%lld",&val[i]);
sort(val+1,val+n+1);
for(int i=n;i>=1;i--){
sum[i]=sum[i+1]+val[i]/x;
t[i]=t[i+1];
Tree::modify(t[i],0,x,val[i]%x,1);
}
for(int i=1;i<=m;i++){
int y;scanf("%lld",&y);
printf("%lld\n",solve(y));
}
}
T3:
题目大意:
给一个长度为 n n n字符串,包含 0 − 9 0-9 0−9的整数,要在其中添加 k k k个加号,求所有构成的表达式的值之和。 n ≤ 5 × 1 0 5 n\le5\times10^5 n≤5×105
解析:
对于一个序列:
2
3
3
3
3
3
3
2\qquad3\qquad3\qquad3\qquad3\qquad3\qquad3
2333333
考虑每个数作为个位数,十位数等的贡献,我们对于第三个
3
3
3,假设有
4
4
4个加号,那么首先有:
2
3
3
+
3
3
3
3
2\qquad3\qquad3\space\space+\space\space3\qquad3\qquad3\qquad3
233 + 3333
除了当前的这个位置,其余位置都可以放加号,贡献为:
C
5
3
×
1
0
0
×
3
C_5^3\times10^0\times3
C53×100×3
对于十位数的情况,我们有:
2
3
3
3
+
3
3
3
2\qquad3\qquad3\qquad3\space\space+\space\space3\qquad3\qquad3
2333 + 333
当前放加号的位置和前面的那个位置均不能放加号,贡献为:
C
4
3
×
1
0
1
×
3
C_4^3\times10^1\times3
C43×101×3
以此类推,我们有了一个
O
(
n
2
)
O(n^2)
O(n2)的算法:
for(int i=1;i<=n;i++){
ll tmp=0;
for(int j=1;j<=min(n-i+1,n-k);j++){
if(j==n-i+1){
tmp+=c[n-j+1][k+1]*mul[j-1];
tmp%=Mod;
}
else{
tmp+=c[n-j][k]*mul[j-1];
tmp%=Mod;
}
}
ans+=tmp*a[i];
ans%=Mod;
}
cout<<ans<<endl;
下面考虑优化,非常不显然地打表+猜想可得,原式为:
f
i
=
1
0
n
−
i
−
1
×
C
i
−
1
k
−
1
+
[
i
=
=
n
]
×
(
1
0
n
−
i
×
C
i
−
1
k
)
f_i=10^{n-i-1}\times C_{i-1}^{k-1}+[i==n]\times(10^{n-i}\times C_{i-1}^{k})
fi=10n−i−1×Ci−1k−1+[i==n]×(10n−i×Ci−1k)
a
n
s
=
∑
i
=
1
n
f
i
×
s
i
ans=\sum_{i=1}^{n}f_i\times s_i
ans=i=1∑nfi×si
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353,maxn=500100;
int ksm(int a,int b){
int res=1;
for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) res=1ll*res*a%mod;
return res;
}
int fac[maxn],invfac[maxn],f[maxn];
int C(int n,int m){return (n<0||n<m)?0:1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;}
int n,k;char s[500005];
signed main(){
for(int i=fac[0]=1;i<maxn;i++) fac[i]=1ll*fac[i-1]*i%mod;
invfac[maxn-1]=ksm(fac[maxn-1],mod-2);
for(int i=maxn-2;i>=0;i--) invfac[i]=1ll*invfac[i+1]*(i+1)%mod;
scanf("%d%d",&n,&k);scanf("%s",s+1);
int ans=0;
for(int i=n-1;i;i--) f[i]=(f[i+1]+1ll*ksm(10,n-i-1)*C(i-1,k-1)%mod)%mod;
for(int i=1;i<=n;i++) f[i]=(f[i]+1ll*ksm(10,n-i)*C(i-1,k)%mod)%mod;
for(int i=1;i<=n;i++) ans=(ans+1ll*f[i]*(s[i]-'0')%mod)%mod;
cout<<ans<<endl;
}