AtCoder Beginner Contest 252
Problem A,B
一分钟通过,此处不赘述。
Problem C
由于只有 9 个字符,所以分开处理。
对于一个字符,最好的方案是到一个时间点,有任何一个字符串这个位置有这个字符,就直接选。
但是实际上会出现在多个字符串,有一个位置的字符在多个字符串出现,意思是 \(s[1][i] == s[k][i]\) 的情况。
这很好处理。每一次只能选择一个,那么相同的位置只能在 10 秒后再选择。
所以给每个字符开一个vector,把位置推进去,相同的位置就 +10 ,然后排序,选前 n 个最优。
#include<bits/stdc++.h>
#define int long long
#define mem(x,y) memset(x,y,sizeof(x))
#define frein freopen("in.in","r",stdin)
#define freout freopen("out.out","w",stdout)
using namespace std;
int read(){
int s = 0,w = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')w = -1;ch = getchar();}
while(ch >= '0' && ch <= '9')s = s * 10 + ch - '0',ch = getchar();
return s * w;
}
int n;
string s;
vector <int> q[11];
int ans = 1e18;
int bk[110][110];
signed main(){
cin>>n;
for(int i = 1;i <= n;i ++){
cin>>s;
s = '#' + s;
for(int j = 1;j <= 10;j ++){
if(!bk[s[j] - '0'][j - 1])q[s[j] - '0'].push_back(j - 1);
else q[s[j] - '0'].push_back(j - 1 + bk[s[j] - '0'][j - 1]);
bk[s[j] - '0'][j - 1] += 10;
}
}
for(int j = 0;j <= 9;j ++)sort(q[j].begin(),q[j].end());
for(int j = 0;j <= 9;j ++){
int s = 2147483647;
if(q[j].size() < n)continue;
int cnt = 0;
for(int k = 0;k < q[j].size();k ++){
if(k == 0 || q[j][k] != q[j][k - 1]){
cnt ++;
s = q[j][k];
if(cnt == n)break;
}
}
if(cnt == n)ans = min(ans,s);
}
cout<<ans;
}
Problem D
容斥。
假设三个数为 \(a,b,c\) ,枚举 \(b\) ,然后做一个前缀后缀值域数组。
答案就是 \(a \neq b\) 且 \(c \neq b\) 的个数,减去满足以上一条情况下 \(a = c\) 的个数,再加上满足以上两条情况下 \(a = b\) 且 \(b = c\) 的情况。
#include<bits/stdc++.h>
#define int long long
#define mem(x,y) memset(x,y,sizeof(x))
#define frein freopen("in.in","r",stdin)
#define freout freopen("out.out","w",stdout)
using namespace std;
int read(){
int s = 0,w = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')w = -1;ch = getchar();}
while(ch >= '0' && ch <= '9')s = s * 10 + ch - '0',ch = getchar();
return s * w;
}
int n;
int pre[1000010];
int nxt[1000010];
int a[1000010];
int ans;
int now;
signed main(){
cin>>n;
for(int i = 1;i <= n;i ++)a[i] = read();
for(int i = 2;i <= n;i ++)nxt[a[i]] ++;
for(int i = 2;i <= n - 1;i ++){
nxt[a[i]] --;
now -= pre[a[i]];
now += nxt[a[i - 1]];
pre[a[i - 1]] ++;
ans += (((i - 1 - pre[a[i]]) * (n - i - nxt[a[i]])));
ans -= now;
ans += pre[a[i]] * nxt[a[i]];
}
cout<<ans;
}
Problem E
最短路径树模板。
dijkstra 会用 \(n - 1\) 条边更新。
这些边都是在某条最短路径上的。
所以这样必然优秀。
跑一遍 dijkstra ,把用作更新的 \(n - 1\) 条边记录下来,即为答案。
/*
深山踏红叶,耳畔闻鹿鸣
飘摇风雨中,睹物思故乡
可叹,落叶飘零
*/
#include<bits/stdc++.h>
#define int long long
#define mem(x,y) memset(x,y,sizeof(x))
#define frein freopen("in.in","r",stdin)
#define freout freopen("out.out","w",stdout)
#define debug(x) cout << (#x) << " = " << x << endl;
using namespace std;
int read(){
int s = 0,w = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')w = -1;ch = getchar();}
while(ch >= '0' && ch <= '9')s = s * 10 + ch - '0',ch = getchar();
return s * w;
}
struct node{
int to;
int nxt;
int w;
int id;
}e[1000010];
int cnt,head[1000010];
int n,m;
int dis[1000010];
bool bk[1000010];
int lst[1000010];
vector <int> ans;
void add(int x,int y,int w,int id){
cnt ++;
e[cnt].to = y;
e[cnt].nxt = head[x];
e[cnt].w = w;
e[cnt].id = id;
head[x] = cnt;
}
void dijkstra(){
priority_queue <pair <int,int> > q;
for(int i = 1;i <= n;i ++)dis[i] = 1e18;
dis[1] = 0;
q.push({0,1});
while(!q.empty()){
pair <int,int> now = q.top();
q.pop();
int x = now.second;
if(bk[x])continue;
bk[x] = true;
for(int i = head[x];i;i = e[i].nxt){
int v = e[i].to;
if(dis[v] > dis[x] + e[i].w){
dis[v] = dis[x] + e[i].w;
if(!bk[v])q.push({-dis[v],v});
lst[v] = e[i].id;
}
}
}
}
signed main(){
cin>>n>>m;
for(int i = 1;i <= m;i ++){
int x,y,w;
x = read(),y = read(),w = read();
add(x,y,w,i);
add(y,x,w,i);
}
dijkstra();
for(int i = 2;i <= n;i ++)printf("%lld ",lst[i]);
}
Problem F
合并果子的逆过程,只不过多了个裁剪多余的 kunbar 。
把这个大 kunbar 也推进队列就行了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
priority_queue <int,vector<int>,greater<int> > q;
int n;
int ans;
signed main(){
int m;
cin>>n>>m;
for(int i = 1;i <= n;i ++){
int x;
cin>>x;
q.push(x);
m -= x;
}
if(m){
q.push(m);
}
while(q.size() != 1){
int a = q.top();
q.pop();
int b = q.top();
q.pop();
q.push(a + b);
ans += a + b;
}
cout<<ans;
return 0;
}
Problem G
区间dp。
设置 \(dp_{ij}\) 为 \([i,j]\) 这个区间组成的 dfs 序树个数。
分类讨论,如果要合并区间的话,有两种情况。
第一种是把整个树接上去。
第二种是把树接在大区间根的同层。
第二种就是 \([i + 1,j]\) 这个区间的个数了。
第一种的话,找个点作为连接口,然后把大区间拆开,乘法原理就行了。
要判断是否可以接上去同层,也就是判断大小,因为题目先遍历编号小的点。
#include<bits/stdc++.h>
#define int long long
#define mem(x,y) memset(x,y,sizeof(x))
#define frein freopen("in.in","r",stdin)
#define freout freopen("out.out","w",stdout)
#define debug(x) cout << (#x) << " = " << x << endl;
using namespace std;
int read(){
int s = 0,w = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')w = -1;ch = getchar();}
while(ch >= '0' && ch <= '9')s = s * 10 + ch - '0',ch = getchar();
return s * w;
}
int n;
int a[1000010];
int dp[510][510];
const int mod = 998244353;
signed main(){
cin>>n;
for(int i = 1;i <= n;i ++)a[i] = read();
for(int i = 1;i <= n;i ++)dp[i][i] = 1;
for(int len = 2;len <= n;len ++)
for(int i = 1;i <= n;i ++){
int j = i + len - 1;
if(j > n)break;
dp[i][j] = (dp[i][j] + dp[i + 1][j]) % mod;
for(int k = i + 1;k <= j - 1;k ++){
if(a[i + 1] < a[k + 1])
dp[i][j] = (dp[i][j] + dp[i + 1][k] * dp[k][j] % mod) % mod;
}
}
cout<<(dp[1][n] % mod + mod) % mod<<endl;
}