牛客周赛84
续上周
D
赛时用的数学方法,花的时间和代码量比较多。差分我写得少,这里用题解的差分方法补个题
用差分数组对每个k区间做修改,然后求和以及相邻数字绝对值差对陡峭值的贡献即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <bits/stdc++.h> using namespace std; const int maxn = 1e6+1;
int t[maxn],d[maxn]; int main(void){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int n,k,ans=0; cin >> n >> k; string s; cin >> s; for(int i=1,j=k;j<=n;i++,j++){ d[i]++; d[j]--; } t[0]=0; for(int i=1;i<=n;i++){ t[i] += t[i-1] + d[i]; ans += t[i] * abs(s[i]-s[i-1]); } cout << ans << '\n'; return 0; }
|
E
题目
以1为根节点,用dfs搜出每个子树的陡峭值,然后再用dfs求出每个节点与其子树断开后的陡峭值之差的绝对值,ans取最小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <bits/stdc++.h> #define int long long using namespace std; const int maxn = 1e5+1;
vector <int> G[maxn]; int sum[maxn]={0},ans = 2LL<<60;
void dfs_sum(int u,int f){ for(auto &v:G[u]){ if(v == f) continue; dfs_sum(v,u); sum[u] += sum[v] + abs(u-v); } }
void dfs_split(int u,int f){ for(auto &v:G[u]){ if(v == f) continue; ans = min(ans,abs(sum[v]-(sum[1]-sum[v]-abs(u-v)))); dfs_split(v,u); } } signed main(void){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int n,u,v; cin >> n; for(int i=0;i<n-1;i++){ cin >> u >> v; G[u].push_back(v); G[v].push_back(u); } dfs_sum(1,0); dfs_split(1,0); cout << ans << '\n'; return 0; }
|
CF1006 div.3
A

求最少操作次数,贪心,如果n*p < |k|了,肯定不行,否则就是k/p
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <bits/stdc++.h> using namespace std;
int main(void){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int t; cin >> t; while(t--){ int n,k,p; cin >> n >> k >> p; if(n*p < abs(k)){ cout << "-1\n"; } else cout << ceil(1.0*abs(k)/p) << '\n'; } return 0; }
|
B

可以发现,数量最多的情况就是"--“尽可能呈对称的情况,”"全都放在中间。
特判就是n<3的时候无法组成图案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <bits/stdc++.h> #define int long long using namespace std;
signed main(void){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int t; cin >> t; while(t--){ int n; string s; cin >> n >> s; if(n < 3){ cout << "0\n"; continue; } int num1 = 0,num2 = 0,si = s.size(); for(int i=0;i<si;i++){ if(s[i] == '-') num1++; else num2++; } int a1 = num1/2, a2 = num1-a1; cout << a1*num2*a2 << '\n'; } return 0; }
|
C

贪心,用num来模拟每个数按位或
因为按位或不会增加二进制位数,所以当第n个数或后都还小于x的话,直接输出x;中间某个数或x后大于x了(即x的二进制的某位是0),那么从这个数以后都输出x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <bits/stdc++.h> using namespace std;
int main(void){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int t; cin >> t; while(t--){ int n,x; cin >> n >> x; int num=0; for(int i=0;i<n;i++){ num |= i; if((num | x) > x){ for(;i<n;i++) cout << x << ' '; break; } if(i == n-1 && num < x){ cout << x << ' '; break; } cout << i << ' '; } cout << '\n'; } return 0; }
|
D

贪心地想,要使得交换后逆序对尽可能少,那么就要让区间(l,r)上比al大的尽可能少,比al小的尽可能多。用da记录比al大的,xiao记录比al小的,xiao-da的值越大,就说明l和r交换后逆序对越少
这个算法是O(n2)的复杂度,题目给的数据范围内是可接受的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <bits/stdc++.h> using namespace std; vector <int> a(2001);
int main(void){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int t; cin >> t; while(t--){ int n; cin >> n; a.clear(); for(int i=1;i<=n;i++) cin >> a[i]; int ans=0,l=1,r=1; for(int i=1;i<=n;i++){ int da=0, xiao=0; for(int j=i;j<=n;j++){ if(a[j] > a[i]) da++; else if(a[j] < a[i]) xiao++; if(ans < xiao - da){ ans = xiao - da; l=i,r=j; } } } cout << l << ' ' << r << '\n'; } return 0; }
|
状压DP
状压DP是DP的一种优化方法
Hamilton旅行商问题

Hamilton问题是NP问题,没有多项式复杂度的解法,暴力解法复杂度是O(n*n!),而状压DP可将复杂度将为O(n2*2n)
设S是图的一个子集;
dp[S][j]:表示“集合S内的最短Hamilton路径”,即从起点0出发经过S中所有点,到达终点j时的最短路径;集合S中包括j点;
让S从最小的子集逐步拓展到整个图,最后得到的dp[N][n-1]就是答案,N表示包含图上所有点的集合。
(S-j表示从集合S中去掉j,即不包含j点的集合)
设k为S-j中一个点,把从0到j的路径分为两部分:(0->…->k)+(k->j)。以k为变量枚举S-j中所有的点,找出最短路径
状态转移方程为:
dp[S][j] = min(dp[S-j][k] + dist(k,j))
状态压缩DP的技巧:用一个二进制数表示集合S,即把S压缩到一个二进制数中,S的每一位表示图上的一个点,0表示不包含,1表示包含
例如S = 0000 0101,表示包含2,0这两个点
if((S>>j) & 1)
,判断当前的集合S中是否含有j点
if((S^(1<<j))>>k & 1)
,其中S^(1<<j)
的作用是从集合中去掉j点,得到集合S-j,然后>> k & 1
表示用k遍历集合中的1,这些1就是S-j中的点,这样就实现了"枚举集合S-j中所有的点" (S^(1<<j))
也可以写成S-(1<<j)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <bits/stdc++.h> using namespace std; int n,dp[1<<20][21]; int dist[21][21];
int main(void){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); memset(dp,0x3f,sizeof(dp)); cin >> n; for(int i=0;i<n;i++) for(int j=0;j<n;j++) cin >> dist[i][j]; dp[1][0] = 0; for(int S=1;S<(1<<n);S++) for(int j=0;j<n;j++) if((S>>j) & 1) for(int k=0;k<n;k++) if((S^(1<<j)) >> k & 1) dp[S][j] = min(dp[S][j],dp[S^(1<<j)][k] + dist[k][j]); cout << dp[(1<<n)-1][n-1] << '\n'; return 0; }
|