题目传送门:#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
m≥n∗k)。
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
n∗k。
输出一种可能的删除方案,如果没有,输出
−
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
1≤n,k,m≤5∗105,1≤s≤k,
a
i
,
b
i
≤
5
∗
1
0
5
a_i,b_i\le 5*10^5
ai,bi≤5∗105
题目分析:
对于
a
a
a序列中的一段区间
[
l
,
r
]
[l,r]
[l,r],可以得出它如果要被放到切出的某一段中,需要删除的个数是
r
−
⌈
l
k
⌉
∗
k
r-\lceil\frac lk\rceil*k
r−⌈kl⌉∗k,所以很容易判断一段区间是否取得到。
那么从小到大枚举左端点
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
1≤i≤n−1,同时需要保证所有的
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
n≤105
题目分析:
如果可以把
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...ti−1的一个子串,可以花费 b b b个硬币划分出这一段。
求最小花费。 1 ≤ n , a , b ≤ 5000 1\le n,a,b\le5000 1≤n,a,b≤5000
题目分析:
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[i−1]≤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[i−1][j−1]+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[i−mx[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
2≤n≤200000,0≤ci≤109
题目分析:
法一:
子树对应的叶节点一定是一段连续的区间,那么问题相当于一段整数序列
a
i
a_i
ai,需要知道多少区间的前缀和之差,才能够求出每个位置上的元素值。
把问题转化为图论,一个节点可控制的叶节点区间是
[
l
,
r
]
[l,r]
[l,r],那么连一条
(
l
−
1
,
r
)
(l-1,r)
(l−1,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̸=v∑f[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
2≤a≤103),求一个数
n
n
n使得
S
(
a
∗
n
)
∗
a
=
S
(
n
)
S(a*n)*a=S(n)
S(a∗n)∗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
≤5∗105
题目分析:
一看就是个数学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
a≤1000,所以
i
≤
1000
i\le1000
i≤1000,而CF的题解上说可以去掉
∣
j
∣
>
a
|j|>a
∣j∣>a的状态,所以
−
1000
≤
j
≤
1000
-1000\le j\le1000
−1000≤j≤1000.(然而为什么可以去掉并不知道。。欢迎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∗(tb−ta)
n ≤ 1 0 5 , c ≤ 1 0 2 , d ≤ 1 0 8 n\le 10^5,c\le10^2,d\le 10^8 n≤105,c≤102,d≤108
求所有事件送信的花费最小值。
题目分析:
会发现当信第一次(
t
i
t_i
ti)送到R先生处时,就一定会有一个
c
∗
(
t
n
−
t
i
)
c*(t_n-t_i)
c∗(tn−ti)的代价,因为要取信就必须放信,两人不论交替或不交替,都至少会有一封信一直留到
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);
}