ICPC 2019 Pacific Northwest (Div. 1)
2019-2020 ACM-ICPC Pacific Northwest Regional Contest (Div. 1)
A Radio Prize
大意:
求出一颗树上每个点到其他点的距离的和,两个点之间的距离定义为两个点的权值和乘上路径权值和
思路:
明显是换根DP,不过写的时候写了好久...
将所求拆成两个数:这个点的权值乘路径权值和+目标点的权值乘路径权值和,这样就可以维护了,换根的时候求出父节点到这个节点的贡献即可
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
#define int LL
int n, a[N], sumv[N];
int SUMW = 0;
int sumV[N];
vector<pair<int, int>> mp[N];
int dp[N], dis[N][2], sz[N];
int res[N];
void dfs1(int now, int fa) {
sumv[now] = a[now];
sz[now] = 1;
for (int i = 0; i < mp[now].size(); i++) {
int ne = mp[now][i].first, w = mp[now][i].second;
if (ne == fa) continue;
dfs1(ne, now);
sz[now] += sz[ne]; //子节点数量
dp[now] += dp[ne] + w * sumv[ne]; //子节点的贡献
sumv[now] += sumv[ne]; //子节点权值和
dis[now][0] += dis[ne][0] + w * sz[ne]; //子节点距离和
}
}
void dfs2(int now, int fa) {
for (int i = 0; i < mp[now].size(); i++) {
int ne = mp[now][i].first, w = mp[now][i].second;
if (ne == fa) continue;
dis[ne][1] = dis[now][1] - w * sz[ne] + w * (n - sz[ne]);
res[ne] = dp[ne] + dis[ne][1] * a[ne] + res[now] -
dis[now][1] * a[now] - (dp[ne] + w * sumv[ne]) +
w * (SUMW - sumv[ne]);
dfs2(ne, now);
}
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i],SUMW += a[i];
for (int i = 1; i <= n - 1; i++) {
int x, y, w;
cin >> x >> y >> w;
mp[x].push_back({y, w}), mp[y].push_back({x, w});
}
dfs1(1, 0);
res[1] = dp[1] + dis[1][0] * a[1];
dis[1][1] = dis[1][0];
dfs2(1, 0);
for (int i = 1; i <= n; i++) cout << res[i] << endl;
return 0;
}
B Perfect Flush
大意:
给出n个数,n个数都是由1到k的数组成,现在要从里面找到一个字典序最小的1到k的全排列
思路:
用栈去维护,如果当前栈顶的数字在后面还会出现,且比当前要入栈的数字大,那么就弹栈
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const MAXN = 2e5 + 10;
int n, k;
int a[MAXN];
int pos[MAXN];
stack<int> sta;
int vis[MAXN];
vector<int> ans;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
pos[a[i]] = i;
}
for (int i = 1; i <= n; i++) {
if (vis[a[i]]) continue;
while (sta.size()) {
int t = sta.top();
if (a[i] >= t) break;
if (pos[t] < i) break;
vis[t] = 0;
sta.pop();
}
sta.push(a[i]);
vis[a[i]] = 1;
}
while (sta.size()) {
//cout << sta.top() << " ";
ans.push_back(sta.top());
sta.pop();
}
for (int i = ans.size() - 1; i >= 0; i--) {
cout << ans[i] << " ";
}
return 0;
}
C Coloring Contention
大意:
给出一个无向图,Bob要从1号点走到n号点,Alice现在要给每条边染色,他可以染红色或者蓝色,Alice想让Bob经过的路径中颜色变换次数最多,Bob则想让自己经过的颜色变换次数最少,问Alice能让Bob经过的颜色变换次数最多是多少次
思路:
直接bfs,求出1到n的最短路,然后最短路径-1即可
#include <bits/stdc++.h>
#define int long long
#define debug(x) cout << #x << " = " << x << endl;
using namespace std;
int const MAXN = 2e5 + 10, MAXM = MAXN * 2;
int n, m, T, e[MAXN], ne[MAXM], h[MAXN], idx, st[MAXN], dis[MAXN];
queue<int> q;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void bfs(int S) {
q.push(S);
st[S] = 1;
while(q.size()) {
auto t = q.front();
q.pop();
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (st[j]) continue;
st[j] = 1;
dis[j] = dis[t] + 1;
q.push(j);
}
}
}
signed main() {
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1, a, b; i <= m; ++i) {
cin >> a >> b;
add(a, b), add(b, a);
}
bfs(1);
cout << max((long long)0, dis[n] - 1);
return 0;
}
D Dividing by Two
大意:
给出两个点A和B,现在每次操作可以将A除以2(此时A必须为偶数),或者将A加1,问最少多少次操作可以将A变换为B
思路:
当A小于B时,肯定只能不停+1
当A大于B时,首先将其变换到B到2*B 的区间,然后判断先除后减还是先减后除
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
#define int LL
int a, b;
signed main() {
cin >> a >> b;
if (a <= b) {
cout << b - a << endl;
} else {
int res = 0;
swap(a, b);
while (1) {
if (b >= a && b <= 2 * a) {
int tmp;
if (b % 2 == 0)
tmp = min(2 * a - b + 1, 1 + a - b / 2);
else
tmp = min(2 * a - b + 1, 2 + a - (b + 1) / 2);
cout << res + tmp << endl;
return 0;
}
if(b%2==0){
res++;
b /= 2;
}
else{
res++;
b++;
}
}
}
return 0;
}
E Rainbow Strings
大意:
给定一个长度为n字符串,要求从中选择一些子序列,使得子序列中每种字符只出现一次,问这样的子序列有多少个?
思路:
可以计算一下原来的字符串中每个字符出现的次数,那么子序列就是每次从不同的字符串挑选出一个或者不选的方案,即(c[0] + 1) * (c[1] + 1) * (c[2] + 1) * ... * (c[n] + 1)
#include <bits/stdc++.h>
using namespace std;
#define int long long
int const MAXN = 2e5 + 10;
int const mod = 11092019;
int n, m, T;
int num[MAXN];
signed main() {
string s;
cin >> s;
for (int i = 0; i < s.size(); i++) {
num[s[i] - 'a']++;
}
int ans = 1;
for (int i = 0; i < 26; i++) {
ans = ans * (num[i] + 1) % mod;
}
cout << ans;
return 0;
}
I Error Correction
大意: 给定n个字符串,每个字符串中不存在重复出现的字符。现在要从这n个字符串中选择一个子集,使得子集中不存在两个字符串仅仅交换2个字符就会完全相同的情况。问这样的子集最大是多大。\(1<=n<=500\)
思路:
最大独立集。对于每个字符串看作一个点,如果2个字符串通过交换2个字符会相同,那么连一条边,那么就是找到一个最大的独立集。那么答案就是n - 最大匹配数目。
#include <bits/stdc++.h>
#define int long long
#define debug(x) cout << #x << " = " << x << endl;
using namespace std;
int const MAXN = 5e2 + 10;
int n, m, T, g[MAXN][MAXN], st[MAXN], match[MAXN];
string s[1000];
int check(int x, int y) {
string s1 = s[x], s2 = s[y];
int len = s1.size();
int cnt = 0;
for (int i = 0; i < len; ++i) {
if (s1[i] != s2[i]) cnt++;
}
return cnt == 2;
}
bool find(int x) {
for (int i = 1; i <= n; ++i) {
if (!g[x][i]) continue;
if (!st[i]) {
st[i] = 1;
if (!match[i] || find(match[i])) {
match[i] = x;
return true;
}
}
}
return false;
}
int hungary() {
int ans = 0;
for (int i = 1; i <= n; ++i) {
memset(st, 0, sizeof st);
if (find(i)) ans++;
}
return ans;
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> n;
for (int i = 1; i <= n; ++i) cin >> s[i];
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (i != j && check(i, j)) g[i][j] = g[j][i] = 1;
}
}
cout << n - hungary() / 2;
return 0;
}
J Interstellar Travel
大意:
给出n个点,每个点都有三个属性T S A,每个点的贡献\(max(0,Ti−si·dist(ai,a))\)
a为飞船飞行的角度,所以对于每个飞行角度,n个点的总贡献T是一定的,现在要求求出最大的T
思路:
据说正解是求出函数关系式,不过写了一发模拟退火+卡时过了...
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int n;
long double t[N], s[N], a[N];
const long double eps = 1e-8L;
const long double pi = acos(-1.0L);
int sgn(long double x) {
if (fabs(x) < eps) return 0;
if (x < 0)
return -1;
else
return 1;
}
long double res = 0;
long double getsum(long double x) {
long double re = 0;
for (int i = 0; i < n; i++) {
long double dis = fabs(x - a[i]);
if (sgn(dis-pi) >= 0) dis=2.0L*pi-dis;
re += max(0.0L, t[i] - s[i] * dis);
}
res = max(res, re);
return re;
}
long double rand(long double l, long double r) {
return (long double)rand() / RAND_MAX * (r - l) + l;
};
void sa() {
long double p = rand(0.0L, 2.0L * pi);
for (long double t = 2.0L * pi; t > 1e-8L; t *= 0.99L) {
long double np = rand(max(0.0L,p - t), min(2.0L*pi,p + t));
long double dt = getsum(np) - getsum(p);
if (exp(dt / t) > rand(0, 1)) {
p = np;
}
}
}
int main() {
cin >> n;
srand((unsigned)time(NULL));
for (int i = 0; i < n; i++) {
cin >> t[i] >> s[i] >> a[i];
}
while ((double)clock() / CLOCKS_PER_SEC < 3.0) sa();
printf("%0.7Lf\n", res);
return 0;
}
L Carry Cam Failure
大意:
定义不进位计算为无论对于加法还是乘法,所有的进位全部不计算。现在给你N,要求判断是否存在a,使得a * a = N。如果不存在,那么打印-1
思路:
模拟题。直接模拟每一位出现的情况,然后剪枝。每次的情况最多为2,复杂度为:\(O(2^{n/2})\)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const MAXN = 30;
int n, m, T;
string s;
int flag;
int pos;
int bit[MAXN];
int need[MAXN];
int sum[MAXN];
bool check(int k) {
if (k == pos - 1 && bit[k] * bit[k] % 10 == need[k * 2]) return 1;
if (k != pos - 1 && (sum[k + pos - 1] + bit[k] * bit[pos - 1] * 2) % 10 ==
need[k + pos - 1])
return 1;
return 0;
}
void dfs(int k) {
// 搜索到头,扫一遍与输入比较
if (k == -1) {
flag = 1;
for (int i = pos - 1; i >= 0; i--) {
if (sum[i] % 10 != need[i]) {
flag = 0;
return;
}
}
return;
}
if (flag) return;
for (int i = 0; i < 10; i++) {
if (flag) return;
bit[k] = i;
if (check(k)) {
for (int j = pos - 1; j > k; j--)
sum[j + k] += 2 * (bit[k] * bit[j]) % 10;
sum[2 * k] += bit[k] * bit[k] % 10;
dfs(k - 1);
if (flag) return;
for (int j = pos - 1; j > k; j--)
sum[j + k] -= 2 * (bit[k] * bit[j]) % 10;
sum[2 * k] -= bit[k] * bit[k] % 10;
}
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> s;
int len = s.size();
if (len % 2 == 0) return puts("-1"), 0;
for (int i = 0; i < len; i++) need[i] = s[len - i - 1] - '0';
pos = (len + 1) / 2;
dfs(pos - 1);
if (flag) {
for (int i = pos - 1; i >= 0; i--) cout << bit[i];
} else
puts("-1");
return 0;
}
M Maze Connect
大意:
给定一个迷宫网格,每个字符为"\", "/", "."。问你这个迷宫网格中有多少个简单环。
样例解释如下:
4 4
/\..
\.\.
.\/\
..\/
这样是2个环
思路:
dfs找简单环,每次走过就打个标记,遇到打过标记的点就说明找到了环。然后每次dfs找到的环除以2就是找到的环数目
#include <bits/stdc++.h>
#define int long long
using namespace std;
int const MAXN = 1e3 + 10, pMAXN = MAXN * MAXN, pMAXM = pMAXN * 4 * 2;
int n, m, T, e[pMAXM], ne[pMAXM], h[pMAXN], idx, vis[pMAXN], res, tmp;
char s[MAXN][MAXN];
int get(int x, int y) {
return x * (m + 1) + y;
}
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u, int fa) {
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue;
if (vis[j] == 1) {
tmp ++;
continue;
}
vis[j] = 1;
dfs(j, u);
}
return;
}
signed main() {
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < n; ++i) scanf("%s", s[i]);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (s[i][j] == '/') add(get(i + 1, j), get(i, j + 1)), add(get(i, j + 1), get(i + 1, j));
if (s[i][j] == '\\') add(get(i, j), get(i + 1, j + 1)), add(get(i + 1, j + 1), get(i, j));
}
}
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= m; ++j) {
if (vis[get(i, j)]) continue;
vis[get(i, j)] = 1;
tmp = 0;
dfs(get(i, j), -1);
res += tmp / 2;
}
}
cout << res;
return 0;
}