Codeforces Educational Codeforces Round 61 A B C D F
来补补题解啦
A . Regular Bracket Sequence
https://codeforces.com/contest/1132/problem/A
题意 给你四种括号的个数 问你能否组成合法括号数
括号数1 ((
括号数2 ()
括号数3 )(
括号数4 ))
我们发现括号数2 一定符合,所以只有括号数3当一起连起来的时候 他的左边缺一个括号1 右边缺一个括号4 补了以后 剩下的括号1和括号4要匹配 所以我们得到 2无所谓 有3一定有1和4 1必须和4一样 AC
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <vector>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
int main()
{
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
if(a==0&&b==0&&c==0&&d==0)
{
printf("1\n");
return 0;
}
if(a!=d||c&&!a||c&&!d)
{
printf("0\n");
return 0;
}
printf("1\n");
return 0;
}
B . Discounts
https://codeforces.com/contest/1132/problem/B
题意 给你n个商品 m 个折扣券 折扣券的使用条件为买qi个东西 你只需要付qi-1个最贵的商品的钱 那我们知道 如果使用折扣券一定是要让最贵的能免单 那么就是第q个免单 所以按从大到小排序 折扣券就是总和减去第qi大的商品的价格
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <vector>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
const int MAX_N = 300025;
long long arr[MAX_N];
int q[MAX_N];
bool cmp(long long a,long long b)
{
return a>b;
}
int main()
{
int n;
long long sum = 0;
scanf("%d",&n);
for(int i = 1;i<=n;++i) scanf("%lld",&arr[i]),sum+=arr[i];
sort(arr+1,arr+1+n,cmp);
int m ;
scanf("%d",&m);
for(int i = 1;i<=m;++i) scanf("%d",&q[i]);
for(int i = 1;i<=m;++i)
{
printf("%lld\n",sum-arr[q[i]]);
}
return 0;
}
C . Painting the Fence
https://codeforces.com/contest/1132/problem/C
题意 给你$q$个线段 问你$q-2$个区间组合能形成的区间包含最大的数
做过一道牛客类似的取$q-1$个区间组合形成的 就是开一个前缀和记录该点只出现一次的有多少个
既然是q-2那么就是开第二个前缀和 该点出现了两次有多少个
然后n^2暴力枚举 i 和 j 答案就是所有的减去 sum[1][arr[i].r]-sum[1][arr[i].l-1] 和 sum[1][arr[j].r]-sum[1][arr[j].l-1]
但是如果两个区间本身有包含 那就要减去sum2的情况了 具体见代码吧
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <vector>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
const int MAX_N = 5025;
int cnt[MAX_N];
struct node
{
int l,r;
}arr[MAX_N];
int sum[3][MAX_N];
int main()
{
int n,m,maxx=-1,ans = 0,ans_=-1;
scanf("%d%d",&n,&m);
for(int i = 1;i<=m;++i)
{
scanf("%d%d",&arr[i].l,&arr[i].r);
for(int j = arr[i].l;j<=arr[i].r;j++)
cnt[j]++;
}
for(int i = 1;i<=n;++i)
{
if(cnt[i]==1) sum[1][i] = sum[1][i-1]+1;
else sum[1][i] = sum[1][i-1];
if(cnt[i]==2) sum[2][i] = sum[2][i-1]+1;
else sum[2][i] = sum[2][i-1];
if(cnt[i]) ans++;
}
for(int i = 1;i<=m;i++)
{
for(int j = i+1;j<=m;++j)
{
int now = ans - (sum[1][arr[j].r]-sum[1][arr[j].l-1])-(sum[1][arr[i].r]-sum[1][arr[i].l-1]);
int ll = min(arr[i].r,arr[j].r);
int rr = max(arr[i].l,arr[j].l);
if(ll>=rr) now-=(sum[2][ll]-sum[2][rr-1]);
ans_ = max(ans_,now);
}
}
printf("%d\n",ans_);
return 0;
}
D. Stressful Training
https://codeforces.com/contest/1132/problem/D
题意 n个人 每个人都有一个电脑 都有初始电量和每分钟使用多少点 你有一个充电器可以给电脑充电 每一秒只能给一台电脑充
电脑电量可以为0 问你最小需要的充电功率
我们容易想到用二分答案 但是check函数需要特别费心思 我们首先把每个电脑是否需要充电判断一下 比如比赛进行m秒 那么你电脑实际上是只用了m-1秒的电的 因为你刚开机不用 所以我们可以让 a[i](初始电量) - (m-1)*b[i] 是否小于0判断是否需要充电 如果需要充电 我们就要让 cnt[a[i]/b[i]+1]++ 代表第几秒充电 并且处理出来 这样我们知道最多可以充电m-1次 并且要满足到第i秒充电的次数不能大于i cnt数组的意思是 cnt[i] 代表第i秒的充电次数 所以我们就可以解决这道题了
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <vector>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
int n,m;
const int MAX_N = 200025;
int cnt[MAX_N];
long long a[MAX_N],b[MAX_N];
bool check(long long mid)
{
int k = m;
memset(cnt,0,sizeof(cnt));
for(int i = 1;i<=n;++i)
{
long long tmp = a[i];
if(tmp>=1ll*m*b[i]) continue;
cnt[min(tmp/b[i]+1,1ll*(m+1))]++;
while(tmp<1ll*m*b[i])
{
if(k==0) break;
tmp+=mid;
cnt[min(tmp/b[i]+1,1ll*(m+1))]++;
k--;
}
}
long long tot = 0;
for(int i = 1;i<=m;i++)
{
tot += cnt[i];
if(tot>i) return 0;
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
m--;
for(int i = 1;i<=n;++i) scanf("%lld",&a[i]);
for(int i = 1;i<=n;++i) scanf("%lld",&b[i]);
long long l = 0, r = 1e18;
while(l<=r)
{
long long mid = (l+r)>>1;
if(check(mid)) r = mid - 1;
else l = mid + 1;
}
if(l>=1e18)
{
printf("-1\n");
return 0;
}
else printf("%lld\n",l);
return 0;
}
E . Knapsack
https://codeforces.com/contest/1132/problem/E
题意 给你一个背包容量 然后1到8的物体有多少个 问题最多背包能装多少重量
如果容量很小 我们可以用完全背包的重量可达去做 也就是下代码中的dp部分
但是容量很大 所以在大范围 我们需要去贪心 贪心就是你想要重量最多 那么你肯定要把小的物品尽量装进来 小范围去更新
小范围如何确定呢 我们发现 lcm(1,2,3,4,5,6,7,8) = 840 也就是说840是最小的都可以取i个的状态 所以我们利用这个性质 把W分成W-840 贪心 840内背包 但是我们知道实际上W-840是不能被完全重量可达的 所以我们要统计一个ans 记录最多有多少重量可达 那么W-ans就是剩下需要去背包的范围 不妨设大一点 840*8即可 分析复杂度 为 8 * 840 * 8 * 840 代码如下
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <vector>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
long long cnt[15],W,Left[15];
bool dp[10][10005];
int main()
{
long long sum = 0,maxx = 0;
scanf("%lld",&W);
for(int i = 1;i<=8;++i) scanf("%lld",&cnt[i]),sum+=cnt[i]*i;
W = min(sum,W);
for(int i = 1;i<=8;++i)
{
Left[i] = min(cnt[i],840LL/i);
cnt[i] -= Left[i];
}
long long ans = 0;
long long tot = max(0LL,W-840);
for(int i = 1;i<=8;++i)
{
long long amount = min(cnt[i],(tot-ans)/i);
ans+= amount*i;
}
dp[0][0] = 1;
for(int i = 1;i<=8;++i)
{
for(int j = 0;j<=840*8;j++)
{
for(int k = 0;k<=Left[i]&&k*i<=j;k++)
{
dp[i][j]|=dp[i-1][j-k*i];
}
}
}
for(int i = 1;i<=W-ans;i++)
{
if(dp[8][i]) maxx = max(maxx,i + ans);
}
printf("%lld\n",maxx);
return 0;
}
F. Clear the String
https://codeforces.com/contest/1132/problem/F
F题题意 给你一组字符串 相同的可以一笔勾销 问你需要几步能删除所有的字符串
关键在于转化题意 你要删除 就是把不一样的颜色化成一样 问题转化一下 这样长度就不用减少 你就可以dp了
这是一个典型的区间dp问题 dp[i][j]代表把第 i 到 第 j 长度的区间内所有字符换成一个颜色需要的代价
那么答案就是dp[1][n]
我们容易知道 当两端颜色相同 dp[l][r] = min(dp[l][r-1],dp[l+1][r]) 就是把除了端点的其他所有点都化成端点的代价
如果颜色不同 dp[l][r] = min(dp[l][k]+dp[k+1][r],dp[l][r]) (l<=k<=r)
就能解决这题了
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <vector>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
const int MAX_N = 525;
long long dp[MAX_N][MAX_N];
char str[MAX_N];
int main()
{
int n;
scanf("%d",&n);
scanf("%s",str+1);
memset(dp,0x3f3f3f3f,sizeof(dp));
for(int i = 1;i<=n;++i)
dp[i][i] = 1;
for(int i = 2;i<=n;++i)
{
for(int j = 1;j<=n-i+1;j++)
{
int l = j,r = j + i - 1;
if(str[l]==str[r]) dp[l][r] = min(dp[l][r-1],dp[l+1][r]);
else for(int k = l;k<=r;++k) dp[l][r] = min(dp[l][r],dp[l][k]+dp[k+1][r]);
}
}
printf("%d\n",dp[1][n]);
return 0;
}
G //做法是学长教的 所以从学长博客扒的 自己实现了下代码
题意
给你一个长度为n的数组,对其中每个长度为k的连续子序列求这个子序列的最长贪心子序列
选定一个数作为第一个数,那么他右面离他最近而且比他大的数作为第二个数,以此类推直到不能再加数这样产生的序列被称为贪心子序列。
对于一个序列选定每一个数作为起点,得到的最长的贪心子序列就是一个序列的最长贪心子序列。
1≤n,k≤106 1 \leq n,k \leq 10^61≤n,k≤10
6
做法
首先这道题一定是滑动窗口来做的。也就是算出第一段的贡献之后,加上一个数的贡献,减去一个数的贡献。
可是我们怎么维护好一些呢,由于要计算每个数靠右的第一个比他大的数,我们这个过程用一个单调栈就能做完。
之后我们把每个数和右边第一个比他大的数建边,我们就可以得到一个森林,再把所有的根同时指向一个虚根,就得到一颗虚树。
我们设树上每个点的权值代表他到虚根这条路径上有多少个点,那么所有点中最大的权值就是答案,那么我们怎么维护加入一个点呢,我们发现,加入一个点的贡献就是把包括他在内的子树中所有点的权值+1,这个用一个基于dfs序的线段树就可以维护,删除一个点的贡献同理,只要将他的子树内所有点的权值-1即可。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <vector>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
const int MAX_N = 1000025;
int in[MAX_N],out[MAX_N],arr[MAX_N],dep,s[MAX_N<<2],col[MAX_N<<2],ans[MAX_N];
stack<int >st;
vector<int > vt[MAX_N];
void dfs(int rt,int fa)
{
in[rt] = ++dep;
for(int i = 0;i<vt[rt].size();i++)
{
int to = vt[rt][i];
if(to==fa) continue;
dfs(to,rt);
}
out[rt] = dep;
}
void up(int rt)
{
s[rt] = max(s[rt<<1],s[rt<<1|1]);
}
void build(int rt,int l,int r)
{
col[rt] = 0;
if(l==r)
{
s[rt] = 0;
return ;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
up(rt);
}
void down(int rt,int l,int r)
{
if(col[rt])
{
int mid = (l+r)>>1;
col[rt<<1]+=col[rt];
col[rt<<1|1] +=col[rt];
s[rt<<1] += col[rt];
s[rt<<1|1] +=col[rt];
col[rt] = 0;
}
}
void update(int rt,int l,int r,int x,int y,int v)
{
if(x<=l&&r<=y)
{
col[rt]+=v;
s[rt]+=v;
return ;
}
int mid = (l+r)>>1;
down(rt,l,r);
if(x<=mid) update(rt<<1,l,mid,x,y,v);
if(mid<y) update(rt<<1|1,mid+1,r,x,y,v);
up(rt);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i<=n;++i) scanf("%d",&arr[i]);
build(1,1,n+1);
for(int i = n;i>=1;i--)
{
while(!st.empty()&&arr[st.top()]<=arr[i]) st.pop();
if(st.empty())
{
vt[0].push_back(i);
}
else
{
vt[st.top()].push_back(i);
}
st.push(i);
}
dfs(0,-1);
for(int i = n-m+1;i<=n;++i) update(1,1,n+1,in[i],out[i],1);
ans[n-m+1] = s[1];
for(int i = n-m;i>=1;i--)
{
update(1,1,n+1,in[i+m],out[i+m],-1);
update(1,1,n+1,in[i],out[i],1);
ans[i] = s[1];
}
for(int i = 1;i<=n-m+1;++i) i==n-m+1?printf("%d\n",ans[i]):printf("%d ",ans[i]);
return 0;
}