【CF】Codeforces Round #543 (Div. 1, based on Technocup 2019 Final Round)
比赛出锅了,,好像题解泄露了,五分钟就unrated,失去灵魂orz
CF
A Diana and Liana
给你一个m长度的序列,删去一些数之后,保持原有顺序,还剩下若干的数字,然后每k个分一段,分前n段。给你一个multisetS集合,保证某一段中必须要有集合中的所有数字(包括至少要求的个数)。 一切数字5e5 对于每一个L,找到一个最小的R,使得[L,R]中包含S集合中的所有数,然后贪心看行不行。可以考虑到对于每个L,其R保证递增,这样我们加个双指针就可以了。#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
const int maxn = 500000;
int a[maxn+5];
int m,k,n,s,CT;
int YO[maxn+5],yoh[maxn+5],woc,wc;
int ANS = 1e9,ps;
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++) {
int x; scanf("%d",&x);
if(YO[x]++==0) woc++;
yoh[x]++;
}
for(int L=1,R=1;L<=m;L++) {
while(R<=m&&woc>0) {
if( (--YO[a[R]]) == 0 ) woc--;
R++;
}
if(woc==0) {
if( (L-1)%k + (R-L-k)<=0 ) {
puts("0"); return 0;
} else {
if( (L-1)%k + (R-L-k) < ANS && m-(L-1)%k-(R-L-k) >= n*k ) {
ANS = (L-1)%k + (R-L-k); ps = L;
}
}
}
if(YO[a[L]]++==0) woc++;
}
if(ANS==1e9) return puts("-1"),0;
printf("%d\n",ANS);
for(int i=(ps-1)/k*k+1;i<ps;i++) {
printf("%d ",i); ANS--;
}
for(int i=ps;i<=m&&ANS;i++) {
if( (yoh[a[i]]) ) {
yoh[a[i]]--;
} else {
printf("%d ",i); ANS--;
}
}
}
C Compress String
给一个5000的字符串s要求将其拆分为t1+t2+t3+t.....如果ti是t1+t2..t_{i-1}的子串,那么需要花费代价b,否则如果ti长度1,代价a,最小代价。 可以很快想到一个dp,但是n^3? 我们发现如果一个模式串在上一位的dp中不能作为子串,那么添加一个字符之后也不会是子串。。。所以又是双指针。。。加个KMP是n^2的。#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int n,a,b;
char ss[5005];
int f[5005],fail[5005];
void getfail(int fr,int to) {
fail[1] = 0; int j = 0;
for(int i=fr+1;i<=to;i++) {
while(j&&ss[i]!=ss[fr+j]) j = fail[j];
if(ss[i]==ss[fr+j]) j++;
fail[i-fr+1] = j;
}
}
bool KMP(int fr,int to,int l,int r) {
if(r-l+1>to-fr+1) return 0;
int j = 0;
j = 0;
for(int i=fr;i<=to;i++) {
while(j&&ss[i]!=ss[l+j]) j = fail[j];
if(ss[i]==ss[l+j]) j++;
if(j==r-l+1) return 1;
}
return 0;
}
int main() {
scanf("%d%d%d",&n,&a,&b);
scanf("%s",&ss[1]);
f[1] = a; int p = 1;
for(int i=2;i<=n;i++) {
f[i] = f[i-1] + a;
while(p<i) {
getfail(p+1,i);
if(KMP(1,p,p+1,i)){
for(int j=p;j<i;j++) f[i] = min(f[i],f[j]+b);
break;
}
p++;
}
}
printf("%d",f[n]);
}
C
题意简述:要求你压缩一个字符串,假设你将S压缩成了t1t2...tk,那么对于一个串ti。 若|ti|=1可以花a价格压缩,如果ti是t1+t2+..t_{i−1}的子集可以花b价格压缩,问压缩的最小代价。#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int n,a,b;
char ss[5005];
int f[5005],fail[5005];
void getfail(int fr,int to) {
fail[1] = 0; int j = 0;
for(int i=fr+1;i<=to;i++) {
while(j&&ss[i]!=ss[fr+j]) j = fail[j];
if(ss[i]==ss[fr+j]) j++;
fail[i-fr+1] = j;
}
}
bool KMP(int fr,int to,int l,int r) {
if(r-l+1>to-fr+1) return 0;
int j = 0;
j = 0;
for(int i=fr;i<=to;i++) {
while(j&&ss[i]!=ss[l+j]) j = fail[j];
if(ss[i]==ss[l+j]) j++;
if(j==r-l+1) return 1;
}
return 0;
}
int main() {
scanf("%d%d%d",&n,&a,&b);
scanf("%s",&ss[1]);
f[1] = a; int p = 1;
for(int i=2;i<=n;i++) {
f[i] = f[i-1] + a;
while(p<i) {
getfail(p+1,i);
if(KMP(1,p,p+1,i)){
for(int j=p;j<i;j++) f[i] = min(f[i],f[j]+b);
break;
}
p++;
}
}
printf("%d",f[n]);
}
D Power Tree
给一棵树,现在A可以从树上面选一些点出来,选ii的花费为ai,然后B给所有叶子结点赋值,然后A可以对所有选出来的点进行一次操作:使得以它为根的子树中的所有叶子结点全部减去一个相同的值,问如果A想保证无论B怎么赋值自己操作之后每个叶子的权值都为0那么选出来点的最少代价是多少并要求列出所有可能被选出的点。 设定f[x][0/1]表示以x的子树中还有一个点没有被覆盖的最小代价。 设定sum[x] 为x的所有儿子的f[y][0]之和。 考虑dp转移之后最后答案f[x][0]。 最后根据你怎么转移的,然后看看能不能对上一个状态逆转移,如果可以就继续搜,搜出所有可能的转移过程,就可以了。 具体look code:#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#define pr pair<int,int>
#define fi first
#define se second
#define int long long
using namespace std;
const int maxn = 2e5+5;
vector<int>ve[maxn];
int n;
int c[maxn];
int fa[maxn];
int sum[maxn];
int dp[maxn][2];
bool vis[maxn][2];
void dfs(int x,int ba) {
fa[x] = ba;
if(ve[x].size()==1&&(ba)) {
dp[x][0] = c[x];
dp[x][1] = 0;
return;
}
for(auto y:ve[x]) {
if(y==ba) continue;
dfs(y,x);
sum[x] += dp[y][0];
}
dp[x][0] = sum[x];
for(auto y:ve[x]) {
if(y==ba) continue;
dp[x][0] = min(dp[x][0],sum[x]-dp[y][0]+dp[y][1]+c[x]);
dp[x][1] = min(dp[x][1],sum[x]-dp[y][0]+dp[y][1]);
}
}
queue<pr>q;
bool KN[maxn];
main() {
memset(dp,0x3f,sizeof dp);
scanf("%lld",&n);
for(int i=1;i<=n;i++) {
scanf("%lld",&c[i]);
}
for(int i=1;i<n;i++) {
int x,y; scanf("%lld%lld",&x,&y);
ve[x].push_back(y);
ve[y].push_back(x);
}
dfs(1,0);
vis[1][0] = 1; q.push(pr(1,0));
while(q.size()) {
int x = q.front().fi; int y = q.front().se; q.pop();
if(fa[x]&&ve[x].size()==1) {
if(!y)KN[x] = 1; continue;
}
if((!y)&&sum[x]==dp[x][0]) {
for(auto p:ve[x]) {
if(p==fa[x]) continue;
if(!vis[p][0]) q.push(pr(p,0)),vis[p][0] = 1;
}
}
int pre = -1;
for(auto p:ve[x]) {
if(p==fa[x]) continue;
if( ( (!y) && dp[x][0] == sum[x] - dp[p][0] + dp[p][1] + c[x] ) || ( (y) && dp[x][1] == sum[x] - dp[p][0] + dp[p][1] ) ) {
if(!y) KN[x] = 1;
if(pre==-1) {
pre = p;
for(auto g:ve[x]) {
if(g==p||g==fa[x]) continue;
if(!vis[g][0]) vis[g][0] = 1 , q.push(pr(g,0));
}
} else if(pre!=-2) {
if(!vis[pre][0]) vis[pre][0] = 1 , q.push(pr(pre,0));
pre = -2;
}
if(!vis[p][1]) vis[p][1] = 1,q.push(pr(p,1));
}
}
}
int ANS = 0;
for(int i=1;i<=n;i++) {
ANS += KN[i];
}
printf("%lld %lld\n",dp[1][0],ANS);
for(int i=1;i<=n;i++) {
if(KN[i]) printf("%lld ",i);
}
}
E
题意简述:给出一个a,(a≤1000),现在要求找出一个n满足:S(a∗n)=S(n) * a S(x)表示x各位数字之和。 数位dp(还是神奇的dp+bfs) f[x][y][0/1]从低位向高位枚举。 x 表示之前的位数 * A 之后累加到这一位上要进的位(这一位以及接下来要加的值)。 y 表示之间的数 * A 的数的 数位和 * A 减去 当前的数位之和。 据说$x\in[0,1000],y\in[-2000,2000] $这样时间复杂度对的。每次转移枚举当前位,并且记录下从哪里转移过来就好。#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
struct nd{
int a,b,c;
};
const int py = 2010;
nd pre[1005][4050][2];
int dig[1005][4050][2];
bool vis[1005][4050][2];
queue<nd>q;
int A;
int st[500005],tp;
void solve() {
for(int i=0;i<=9;i++) {
int nfi = i*A/10;
int tdj = i*A%10;
vis[nfi][py+tdj*A-i][i!=0] = 1;
q.push((nd){nfi,py+tdj*A-i,i!=0});
pre[nfi][py+tdj*A-i][i!=0] = {-1,-1,-1};
dig[nfi][py+tdj*A-i][i!=0] = i;
}
while(q.size()) {
int x = q.front().a; int y = q.front().b; int z = q.front().c;
q.pop();
if(x==0&&y==py&&z) {
bool ff = 0;
while(x+y+z!=-3) {
if(ff||dig[x][y][z]!=0)printf("%d",dig[x][y][z]),ff=1;
auto oo = pre[x][y][z];
x = oo.a; y = oo.b; z = oo.c;
}
return;
}
for(int i=0;i<=9;i++) {
int nfi = (i*A+x)/10;
int tdj = (x+i*A)%10;
int vv = y + tdj*A - i;
bool fl = z | (i!=0);
if(vv>0&&vv<4050&&!vis[nfi][vv][fl]) {
vis[nfi][vv][fl] = 1;
q.push((nd){nfi,vv,fl});
pre[nfi][vv][fl] = (nd){x,y,z};
dig[nfi][vv][fl] = i;
}
}
}
puts("-1");
}
int main() {
scanf("%d",&A);
solve();
}
F
题意简述: 两个人W,P需要互相送信。 现在有n个时间点t1,t2,...,tn,每个时间点W/P负责送信,以及一个结束时间点tn+1。 有两种选择: 直接花d的代价送给对方 将信寄存在R那里,设从寄存到拿信花了T个单位时间,代价是c∗T。 一个人可以在R那儿拿信当且仅当自己去R送信或者时间为tn+1 考虑到一旦有一个人放了信到朋友R那里,那么从该时间点T一直到最后每一刻一定会花c的价格。 (如果W不放了P一定放,P不放了W一定放) 那么我们枚举在从哪里开始放了第一封信到朋友R那里就可以了。#include<iostream>
#include<cmath>
#include<cstring>
#define int long long
using namespace std;
int n,c,d;
int t[100005];
int ty[100005];
char w[100005];
main() {
cin>>n>>c>>d;
for(int i=1;i<=n;i++) {
cin>>t[i]>>w[i];
if(w[i]=='W') ty[i] = 1; else ty[i] = 0;
}
cin>>t[n+1]; ty[n+1] = 3;
int lst = t[n+1];
int oz = 0;
int ans = d*n;
for(int i=n;i>=1;i--) {
if(ty[i]==ty[i+1]) {
oz += min((lst - t[i+1])*c,d);
} else lst = t[i+1];
ans = min(ans,oz + (i-1)*d + (t[n+1]-t[i])*c );
}
cout<<ans;
}