A: Regular Bracket Sequence
判断括号是否匹配,只需要注意到 “()”这样的括号数量无所谓,每一个“((” 一定需要一个 “))” 来和它配对,每一个“)(”,只要有一对 “((” 和 “))” 就可以有任意多个 因为可以做成 “((” “)(””))“这样的形式。那么几个判断即可
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> Pi;
typedef unsigned long long ULL;
int gcd(int a,int b){if (b == 0) return a; return gcd(b , a%b);}
int lcm(int a, int b){ return a/gcd(a,b)*b;}
inline int read(){
int f = 1, x = 0;char ch = getchar();
while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
const int maxn = 1e5 + 10;
int main(){
int cnt1 = read(),cnt2 = read(),cnt3 = read(),cnt4 = read();
if (cnt1 == cnt4){
if (cnt3 > 0 && cnt1 == 0) cout << 0 << endl;
else cout << 1 << endl;
}else{
cout << 0 << endl;
}
return 0;
}
B: Discounts
对于每个q,我们自然想到贪心,去选取最贵的q个东西,然后最贵里面最便宜的那个的价格,就是我们可以便宜的最多的钱
排序,求和,对于每一个q,答案就是总和减去a[n-q+1]
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> Pi;
typedef unsigned long long ULL;
int gcd(int a,int b){if (b == 0) return a; return gcd(b , a%b);}
int lcm(int a, int b){ return a/gcd(a,b)*b;}
inline int read(){
int f = 1, x = 0;char ch = getchar();
while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
const int maxn = 3e5 + 10;
int a[maxn];
int q[maxn];
LL sum[maxn];
int main(){
// freopen("/Users/chutong/data.txt", "r", stdin);
int n = read();
for (int i=1; i<=n; i++) {
a[i] = read();
}
sort(a+1, a+n+1);
for (int i=1; i<=n; i++) {
sum[i] = sum[i-1] + a[i];
}
int m = read();
for (int i=1; i<=m; i++) {
q[i] = read();
printf("%lld\n",sum[n]-a[n-q[i]+1]);
}
return 0;
}
C:Painting the Fence
这是一个很有意思的题,有两种做法,首先讲暴力
我们把每一个给定的[ l , r ] 区间染色,每给一个[l , r],这个区间上的点color[i]++
然后暴力枚举,我当前删掉哪一个区间[ l , r ],首先给区间上的染色擦去 color[i]-- (l <= i <= r)
擦去吼然后最关键的一步,用一个sum数组,sum[i]表示 [1, i] 区间上,有多少个位置 color[i] == 1 ,注意是等于1,同时用 cnt统计一共有多少个位置被染色了 color[i] > 0
之后再在第一层循环里,枚举第二个删掉哪个区间[ l1 , r1], 这时候,我们也希望对这个区间上的染色擦去,但是如果还是循环color[i]--,一定会TLE。我们就能看出sum数组的作用,sum[ri] - sum[li] 就代表,这段区间的染色如果被擦去,那么会有多少个点失去颜色。那么当我们枚举第二个区间的时候,并不需要真的擦去,cnt - (sum[r1] - sum[l1-1]) 就是擦去两段区间后,剩下的还有颜色的点,记录cnt 的最大值,即为答案,复杂度O(n*q + n^2)
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> Pi;
typedef unsigned long long ULL;
int gcd(int a,int b){if (b == 0) return a; return gcd(b , a%b);}
int lcm(int a, int b){ return a/gcd(a,b)*b;}
inline int read(){
int f = 1, x = 0;char ch = getchar();
while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
const int maxn = 3e5 + 10;
int l[maxn],r[maxn],color[maxn];
int sum[maxn];
int main(){
// freopen("/Users/chutong/data.txt", "r", stdin);
int n = read(),q = read();
for (int i=1; i<=q; i++) {
l[i] = read();
r[i] = read();
for (int j=l[i]; j<=r[i]; j++) {
color[j]++;
}
}
int ans = 0;
for (int i=1; i<=q; i++) {
for (int j=l[i]; j<=r[i]; j++) {
color[j]--;
}
int tot = 0;
for (int j=1; j<=n; j++) {
sum[j] = sum[j-1];
if (color[j] > 0){
tot++;
}
if (color[j] == 1){
sum[j] += 1;
}
}
// cout << tot << endl;
for (int j=1; j<=q; j++) {
if (i == j) continue;
int now = tot - sum[r[j]] + sum[l[j]-1];
ans = max(ans,now);
}
for (int j=l[i]; j<=r[i]; j++) {
color[j]++;
}
}
cout << ans << endl;
return 0;
}
第二种做法就是dp了,但是感觉不是很好想,对于每一个点 i ,有很多个区间覆盖在它上面,我们记录这些区间左端点的最小值为 l[i] ,那么用 dp[i] 代表区间[ 1, i ] 中可以达到的最大染色数,就可以得到如下方程
dp[i] = max ( dp[ l[i]-1 ] + i - l[i] + 1 , dp[i] )
dp[i] = max(dp[i],dp[i-1]);
对于 i 位置来说,要得到最大的染色数,就是从最左端的位置 dp[l[i]-1] ,在加上,选取覆盖了当前位置的区间增加的染色数 i - l[i] + 1, 和这个位置本身的值取最大。或者,dp[i] 从i-1位置转移过来,去最大即可
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> Pi;
typedef unsigned long long ULL;
int gcd(int a,int b){if (b == 0) return a; return gcd(b , a%b);}
int lcm(int a, int b){ return a/gcd(a,b)*b;}
inline int read(){
int f = 1, x = 0;char ch = getchar();
while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
const int maxn = 3e5 + 10;
int dp[maxn],l[maxn];
int main(){
// freopen("/Users/chutong/data.txt", "r", stdin);
int n = read(),q = read(),ll,rr;
for (int i=1; i<=n; i++) {
l[i] = INT_MAX;
}
for (int i=1; i<=q; i++) {
ll = read(); rr = read();
for (int j=ll; j<=rr; j++) {
l[j] = min(l[j],ll);
}
}
for (int k=0; k<q-2; k++) {
for (int i=n; i>=1; i--) {
if (l[i] == INT_MAX) continue;
dp[i] = max(dp[l[i]-1]+i-l[i]+1,dp[i]);
}
for (int i=1; i<=n; i++) {
dp[i] = max(dp[i],dp[i-1]);
}
}
printf("%d\n",dp[n]);
return 0;
}
D:Stressful Training
我的作法为O(q*log^2(n)) 的,惊险卡过2972ms(时限3s),优化一下应该可以在2000ms左右(懒),但是应该有更好的做法可以优化掉一个log(n),有很多200ms左右跑过去的,留个坑学习一下。
对于每一个 x 很容易想到二分,这个值满足二分条件。问题的关键在于如何检查当前的 x 是否符合条件,总是觉得每一个需要充电的值都没有一点确定的规律。
所以选择了模拟,可以手写小顶堆,或者优先队列(手写快点),便利一遍时间轴1到k,每一个时间 t 出队一个人,这个人是所有人里面,电最先用完的,我们就先给他冲 1s 的电,然后重新计算用完电的时间,然后塞回队列里面。
如果,有一个人他在出队之后,发现他电用完电时间,比我们当前时间轴上的时间小,就说明我们尽可能的充电,也没办法让他在这个时间有电,因此这样的 x 是不满足条件的,但是如果遍历完时间轴,发现没有这样的点,就说明当前的x,可以完成供电任务。
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, int> pi;
const int maxn = 3e5 + 10;
int n,k;
LL a[maxn],b[maxn],c[maxn];
struct node {
LL tim,id,tot;
bool operator < (const node & a) const{
return this->tim == a.tim ? this->id > a.id : this->tim > a.tim;
}
};
bool check(LL x){
priority_queue<node>pq;
for (int i=1; i<=n; i++)
pq.push((node){a[i]/b[i], i,a[i]});
for (int i=0; i<k; i++) {
node now = pq.top(); pq.pop();
// cout << now.tim << ' ' << now.tot << ' ' << now.id << endl;
if (now.tim < i) return false;
if (now.tim >= k) break;
now.tot += x;
now.tim = now.tot/b[now.id];
pq.push(now);
}
return true;
}
int main(){
// freopen("/Users/chutong/data.txt", "r", stdin);
scanf("%d%d",&n,&k);
for(int i=1; i<=n; i++)
scanf("%lld",&a[i]);
for(int i=1; i<=n; i++)
scanf("%lld",&b[i]);
LL l = 0,r = 1e13;
//if (check(5)) cout << "good" << endl;
while (l != r) {
LL mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
if (l == 1e13) cout << -1 << endl;
else cout << l << endl;
return 0;
}
F:Clear the String
区间dp,dp[i][j] 代表 i 到 j 区间上删除字符串的最小值,每次都从最左边开始删,每删一次+1,删掉一个之后,剩下的串里面,找到一个和当前字符相同的字符在位置k,那么当前位置dp[i][j]最小值 = min(dp[i][j], dp[i][k-1] + dp[k][j]);
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> Pi;
typedef unsigned long long ULL;
int gcd(int a,int b){if (b == 0) return a; return gcd(b , a%b);}
int lcm(int a, int b){ return a/gcd(a,b)*b;}
inline int read(){
int f = 1, x = 0;char ch = getchar();
while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
const int maxn = 1e3 + 10;
char s[maxn];
int n;
int dp[maxn][maxn];
int dfs(int l,int r){
if (l > r) return 0;
if (dp[l][r] != -1) return dp[l][r];
int now = 1 + dfs(l+1, r);
for (int i=l+1; i<=r; i++)
if (s[i-1] == s[l-1])
now = min(now, dfs(l+1, i-1) + dfs(i, r));
dp[l][r] = now;
return dp[l][r];
}
int main(){
// freopen("/Users/chutong/data.txt", "r", stdin);
scanf("%d%s",&n,s);
memset(dp, -1, sizeof(dp));
printf("%d\n",dfs(1, n));
return 0;
}