文章目录
牛客练习赛128(ABCD、思维、01背包、树上DP、并查集)
非数论选手,直接放弃最后两题
A. Cidoai的幂次序列(思维)
考虑特殊情况:1x = 1。这时,如果可以凑出一个 n-1 即可,显然 (n-1){1%2} = n-1。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
ll n, k;
cin >> n >> k;
cout << 2 << endl;
cout << n-1 << " " << 1 << endl;
return 0;
}
B. Cidoai的平均数对(01背包)
第一想法,DP,算时间复杂度,O(5004),超时,需要优化。
考虑平均数约束的作用。首先,bi 不大于 k 的物品,全部选取。这时对于已经被选择的物品,记 x = ∑ ( k − b i ) x = \sum(k - b_i) x=∑(k−bi),可以把问题转化为一个容量为 x 的01背包问题。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 505;
int a[maxn], b[maxn];
int dp[maxn * maxn];
int main(){
int n, k;
cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> a[i] >> b[i];
for(int i = 1; i <= n; i++) b[i] -= k;
int sum_a = 0, sum_b = 0;
for(int i = 1; i <= n; i++){
if(b[i] < 0){
sum_a += a[i];
sum_b += -1 * b[i]; // 富裕的体积收集起来
}
}
for(int i = 1; i <= n; i++){ // 01背包往富裕的体积里装
if(b[i] < 0) continue;
for(int j = sum_b; j >= b[i]; j--){
dp[j] = max(dp[j], dp[j-b[i]] + a[i]);
}
}
cout << sum_a + dp[sum_b] << endl;
return 0;
}
C. Cidoai的树上方案(树上DP)
典中典。
记引入的新点为 now。
首先,在树上没有环。如果新图中出现了三元环,只能是由连接now的边构成了三元环。
如果一点连接 now,其儿子不可连接now,否则会构成三元环。
对于树上任意点x, D P [ x ] [ 0 ] = DP[x][0] = DP[x][0]= 点 x 不连接now时贡献的方案数, D P [ x ] [ 1 ] = DP[x][1] = DP[x][1]= 点 x 连接now时贡献的方案数。
对于任意一个非叶节点 x :
- D P [ x ] [ 0 ] = ∏ ( D P [ y ] [ 0 ] + D P [ y ] [ 1 ] ) , y ∈ ( x 的孩子节点 ) DP[x][0] = \prod(DP[y][0] + DP[y][1]),y\in(x的孩子节点) DP[x][0]=∏(DP[y][0]+DP[y][1]),y∈(x的孩子节点)。x下的各个孩子互不影响,故而为累乘。
- D P [ x ] [ 1 ] = ∏ ( D P [ y ] [ 0 ] ) , y ∈ ( x 的孩子节点 ) DP[x][1] = \prod(DP[y][0]),y\in(x的孩子节点) DP[x][1]=∏(DP[y][0]),y∈(x的孩子节点)。
对于任意叶节点x:
- D P [ x ] [ 0 ] = D P [ x ] [ 1 ] = 1 DP[x][0] = DP[x][1] = 1 DP[x][0]=DP[x][1]=1
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 998244353;
const int maxn = 2e6 + 5;
vector<int> mp[maxn];
ll v[maxn][2];
ll dfs(int root, int flag){ // flag表示root是否连接x
if(v[root][flag]) return v[root][flag]; // 记忆化搜索,否则会超时
if(mp[root].size() == 0) return v[root][flag] = 1;
ll res = 1;
if(flag == 1){ // root链接x
for(auto son : mp[root])
res = res * dfs(son, 0) % mod;
}
else{ // root没链接x
for(auto son : mp[root])
res = res * (dfs(son, 0) + dfs(son, 1)) % mod;
}
return v[root][flag] = res;
}
int main(){
int n;
cin >> n;
for(int i = 2; i <= n; i++){
int x;
cin >> x;
mp[x].push_back(i);
}
ll res = (dfs(1, 0) + dfs(1, 1)) % mod;
cout << res << endl;
return 0;
}
D. Cidoai的字符集合(并查集)
根据题意,把存在相似的歌曲放入一个set,其他歌曲每个单独成为一个set。
存在单词相同的歌曲连接一条边,使用并查集维护set和set的大小。
#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
using namespace std;
const int maxn = 1e6 + 5;
map<string, int> ID;
int ids = 0;
int get_id(string s){ // 给每个单词一个id,方便后续使用
if(ID[s] == 0) ID[s] = ++ids;
return ID[s];
}
set<int> mp[maxn]; // 记录每个旋律里有什么歌
int f[maxn], sz[maxn]; // 并查集用
int find(int x){
if(f[x] == x) return x;
else return f[x] = find(f[x]);
}
void join(int x, int y){
int fx = find(x);
int fy = find(y);
if(fx != fy){
f[fx] = fy;
sz[fy] += sz[fx];
}
}
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i++){
int k;
cin >> k;
string s;
for(int j = 1; j <= k; j++){
cin >> s;
int id = get_id(s);
mp[id].insert(i);
}
}
for(int i = 1; i <= n; i++) f[i] = i, sz[i] = 1;
for(int i = 1; i <= ids; i++){
int u = -1, v = -1;
for(auto x : mp[i]){
if(u == -1) u = x; // 该旋律下的第一首歌
else{
v = x;
join(u, v); // 放入到和第一首歌相同的集合里
}
}
}
int res = 0, flag = 0;
for(int i = 1; i <= n; i++){
if(find(i) == i){ // 是根节点
if(sz[i] > 1) flag = 1;
else res++;
}
}
cout << res + flag << endl;
return 0;
}