A.Permutation
题意:
给定一个质数,要求给出一段的排列,使得或
题解:
暴力枚举或者的情况即可
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e6 + 10; bool vis[maxn]; int main() { int t; cin >> t; while (t--) { int p; cin >> p; for (int i = 0; i <= p; i++) vis[i] = false; vis[0] = true; vector<int> ans; ans.push_back(1); vis[1] = true; bool flag = true; int cnt = 1; ll tmp = 1; while (cnt < p - 1 && flag) { if (!vis[(tmp * 2) % p]) { tmp = (tmp * 2) % p; ans.push_back(tmp); cnt++; vis[tmp] = true; continue; } if (!vis[(tmp * 3) % p]) { tmp = (tmp * 3) % p; ans.push_back(tmp); cnt++; vis[tmp] = true; continue; } flag = false; break; } if (!flag) printf("%d", -1); else for (auto i : ans) printf("%d ", i); puts(""); } return 0; }
C.Decrement on the Tree
题意:
给定一棵个节点的带权树,每次操作可以选择两个节点,使得该节点之间的路径上每条边的权值,询问最少多少次操作可以使得所有权值为。同时给定次操作,每次操作都会改变一条边的权值,每次求最少操作次数
题解:
如果一条边的权值,那么必然要访问两个端点各一次,因此可以把问题转化为求访问端点的次数上来。对于每个端点计算有多少条路径从该端点出发,最终答案就是各端点之和除(因为两个端点才会形成一条路径)。可以发现当其中一条边的权值大于其他所有边的权值时,令最大的权值为,和该点相连的所有边权之和为,当即时,就需要从该节点连出条边,否则当时,当为偶数时不需要以该点为端点连边,为奇数时只需要连出一条边即可。每次更新只需要更新和该边相连的两个节点的信息即可。附上一张别的博主的图。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 5; int x[maxn], y[maxn]; ll w[maxn], sum[maxn]; multiset<ll, greater<ll>> s[maxn]; ll cal(int x) { ll mx = *s[x].begin(); if (mx * 2 > sum[x]) return mx * 2 - sum[x]; return sum[x] % 2; } void add(int p) { sum[x[p]] += w[p]; sum[y[p]] += w[p]; s[x[p]].insert(w[p]); s[y[p]].insert(w[p]); } void update(int p, ll pw) { sum[x[p]] -= w[p]; sum[y[p]] -= w[p]; s[x[p]].erase(s[x[p]].find(w[p])); s[y[p]].erase(s[y[p]].find(w[p])); w[p] = pw; } int main() { int n, m; scanf("%d%d", &n, &m); for (int i = 1; i < n; i++) { scanf("%d%d%lld", &x[i], &y[i], &w[i]); add(i); } ll ans = 0; for (int i = 1; i <= n; i++) ans += cal(i); printf("%lld\n", ans / 2); while (m--) { int p; ll pw; scanf("%d%lld", &p, &pw); ans -= cal(x[p]) + cal(y[p]); update(p, pw); add(p); ans += cal(x[p]) + cal(y[p]); printf("%lld\n", ans / 2); } return 0; }
E.Game
题意:
给定列的俄罗斯方块,每次可以选定一列的某一行开始往左推,过程种遇到和不比它矮的部分可以一起往左,可以推到最左端停止,途中遇到缺口会往下补。询问经过若干次操作最终最长的一列长度为多少
题解:
因为只能往左推,所以只要右边比左边高都能移过来,想一想可以知道就是求最大的前缀和
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 10; ll pre[maxn]; int main() { int t; cin >> t; while (t--) { int n; cin >> n; for (int i = 0; i <= n; i++) pre[i] = 0; ll ans = 0; for (int i = 1; i <= n; i++) { ll x; scanf("%lld", &x); pre[i] = pre[i - 1] + x; ll res = pre[i] / i + (bool)(pre[i] % i); ans = max(res, ans); } printf("%lld\n", ans); } return 0; }
I.Tournament
题意:
有支队伍,要两两进行比赛,要求使得所有队伍停留的时间之和最短(离开时间-到达时间),要求给出一个构造方案
题解:
一开始想的时候先满足,再满足这样,但是发现这样虽然能使最快完成,但是最后的就慢了,因此想的使将每个点的时间平均一下。因此对于支队伍,先让的队伍进行比赛,再让和的队伍比赛,最终队伍两两比赛。以等于为例,首先进行,对于中间的点因为要最晚进,但是要最早走,因此要放在中间,确定的位置以后,在中,应该是最晚走的,因此前面为;而走了以后要走,因此后面为
因此可以得出策略为
for(i=2;i<=n/2;++i) for(j=1;j<i;++j) printf("%d %d\n",j,i); for(i=n/2+1;i<n;++i) for(j=1;j<=n-i;++j) printf("%d %d\n",j,i); for(i=1;i<=n/2;++i) for(j=n-i+1;j<=n;++j) printf("%d %d\n",i,j); for(i=n/2+1;i<n;++i) for(j=i+1;j<=n;++j) printf("%d %d\n",i,j);
发现可以前两部分和后两部分可以合并
#include <bits/stdc++.h> using namespace std; typedef long long ll; int main() { int t; scanf("%d", &t); while (t--) { int n; scanf("%d", &n); for (int i = 2; i <= n; i++) for (int j = 1; j <= min(i - 1, n - i); j++) printf("%d %d\n", j, i); printf("1 %d\n", n); for (int i = 2; i < n; i++) for (int j = max(n - i + 1, i + 1); j <= n; j++) printf("%d %d\n", i, j); } }
J.Identical Trees
题意:
给定两棵有根树,计算第一棵树最少经过多少次节点编号变化可以变成和第二棵一样
题解:
可以想到大体方向上是树形dp,最难的是状态转移,对于若干个父节点和子节点,如果将它们一一对应,其实就有点感觉到想二分图,那么对于当前节点,肯定是要找二分图最小权,而最大权可以求出来,最小权则可以全部取负号,因此状态转移用KN
算法即可
#include <bits/stdc++.h> #define ll long long #define inf 1 << 30 using namespace std; const int MAXN = 510; int mat[MAXN], lx[MAXN], ly[MAXN], slk[MAXN], pre[MAXN]; int nx, ny, ans, mp[MAXN][MAXN]; bool vis[MAXN]; void bfs(int k, int n) { int x, y = 0, yy = 0, dlt; memset(pre, 0, sizeof(pre)); for (int i = 1; i <= n; i++) slk[i] = inf; mat[y] = k; while (1) { x = mat[y], dlt = inf, vis[y] = 1; for (int i = 1; i <= n; i++) if (!vis[i]) { if (slk[i] > lx[x] + ly[i] - mp[x][i]) slk[i] = lx[x] + ly[i] - mp[x][i], pre[i] = y; if (slk[i] < dlt) dlt = slk[i], yy = i; } for (int i = 0; i <= n; i++) { if (vis[i]) lx[mat[i]] -= dlt, ly[i] += dlt; else slk[i] -= dlt; } y = yy; if (mat[y] == -1) break; } while (y) mat[y] = mat[pre[y]], y = pre[y]; } int KM(int n) { memset(lx, 0, sizeof(lx)); memset(ly, 0, sizeof(ly)); memset(mat, -1, sizeof(mat)); for (int i = 1; i <= n; i++) memset(vis, 0, sizeof(vis)), bfs(i, n); int ans = 0; for (int i = 1; i <= n; i++) if (mat[i] != -1) ans += -mp[mat[i]][i]; return ans; } //KM板子 int dp[MAXN][MAXN]; vector<int> T1[MAXN], T2[MAXN]; //存两棵树 void dfs(int p1, int p2) { if (p1 != p2) dp[p1][p2] = 1; //如果当前节点不相等,那么就要修改一次 int sz1 = T1[p1].size(), sz2 = T2[p2].size(); if (sz1 != sz2)// { dp[p1][p2] = 1000; return; } //如果子树大小都不一样,那么说明是无法变成那样的,赋为INF if (!sz1) return; //如果叶节点 for (int i = 0; i < sz1; i++) for (int j = 0; j < sz2; j++) dfs(T1[p1][i], T2[p2][j]); //继续递归 for (int i = 0; i < sz1; i++) for (int j = 0; j < sz2; j++) mp[i + 1][j + 1] = -dp[T1[p1][i]][T2[p2][j]]; //构建二分图 dp[p1][p2] += KM(sz1); //跑KM } int main() { int n, pa, rt1, rt2; scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", &pa); if (pa) T1[pa].push_back(i); else rt1 = i; //父亲为0即为根 } for (int i = 1; i <= n; i++) { scanf("%d", &pa); if (pa) T2[pa].push_back(i); else rt2 = i; } dfs(rt1, rt2); printf("%d\n", dp[rt1][rt2]); }