A - Broadcast Stations
给出一棵树,允许在某些点上添加权值,此时可以覆盖距这个点不超过这个权值的所有点。求要覆盖所有的点需要最少加的权值。
做的时候不会……韩语的题解机翻过来理解了好长时间OOOrz
读了题多半就是树形dp了。首先选取某个点作为根,然后如果在里面某些位置加权值,显然会向上和向下覆盖的。
用dp[i][j]表示在以节点i为根的子树中加权值,最少要覆盖i上面的j层的最小权值和。
cost[i][j]表示在以节点i为根的子树中,使i下面j层的节点都可以被它们各自的子树覆盖掉的最小权值和。
打扰了……这个本弱真的想不到
于是对dp[i][j]转移的过程中,要么在i上加权值j,要么选择i的一个儿子,让这棵子树向上覆盖j+1层,同时其余子树只需要保证j层。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5005;
int n, a, b;
int dp[maxn][maxn], cost[maxn][maxn];
vector <int>G[maxn];
//int sum[maxn];
void dfs(int u, int fa)
{
//memset(sum, 0, sizeof(sum));
vector <int>sum(n+1, 0);
for(int i = 0;i < G[u].size();i++)
{
int v = G[u][i];
if(v == fa) continue;
dfs(v, u);
for(int j = 0;j <= n;j++) sum[j] += cost[v][j];
}
for(int i = 0;i <= n;i++) dp[u][i] = cost[u][i] = n;
for(int i = 0;i < G[u].size();i++)
{
int v = G[u][i];
if(v == fa) continue;
for(int j = 1;j <= n;j++)
dp[u][j-1] = min(dp[u][j-1], dp[v][j] + sum[j-1] - cost[v][j-1]);
}
for(int i = 1;i <= n;i++) cost[u][i] = min(cost[u][i], sum[i-1]);
for(int i = 1;i <= n;i++) dp[u][i] = min(dp[u][i], sum[i] + i);
for(int i = n-1;i >= 0;i--) dp[u][i] = min(dp[u][i], dp[u][i+1]);
cost[u][0] = dp[u][0];
for(int i = 1;i <= n;i++) cost[u][i] = min(cost[u][i], cost[u][i-1]);
}
int main()
{
scanf("%d", &n);
for(int i = 1;i < n;i++)
{
scanf("%d%d", &a, &b);
G[a].push_back(b), G[b].push_back(a);
}
dfs(1, 0);
printf("%d\n", dp[1][0]);
return 0;
}
B - Connect3
两个人轮流往四个栈里面加东西,如果某个人加的东西有三个连成一条线,他就赢了。给出第一个和最后一个放置的坐标,问有多少种不同的最终状态。
直接暴搜,0,1,2分别表示没放、黑棋、白棋,把这个4*4的东西hash掉。就是在判定结束的时候有些恶心。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 12;
int x, a, b, ans;
int g[maxn][maxn], cnt[maxn];
string Hash()
{
string ans = "";
for(int i = 1; i <= 4;i++)
for(int j = 1; j <= 4;j++) ans += g[i][j] + '0';
return ans;
}
map<string, bool> mp;
int mov[8][2] = {0, 1, 0, -1, 1, 0, -1, 0, -1, -1, -1, 1, 1, -1, 1, 1};
bool ok(int x, int y)
{
if (x < 0 || x > 4 || y < 0 || y > 4) return false;
return true;
}
bool check(int x, int y)
{
int now = g[x][y];
for(int i = 0; i < 8; i++)
{
int nx = x + mov[i][0];
int ny = y + mov[i][1];
int nnx = nx + mov[i][0];
int nny = ny + mov[i][1];
if (ok(nx, ny) && ok(nnx, nny))
if (g[nx][ny] == now && g[nnx][nny] == now)
return true;
nx = x + mov[i][0];
ny = y + mov[i][1];
nnx = x - mov[i][0];
nny = y - mov[i][1];
if (ok(nx, ny) && ok(nnx, nny))
if (g[nx][ny] == now && g[nnx][nny] == now)
return true;
}
return false;
}
void dfs(int x, int y, int vis)
{
if(check(x, y))
{
if(vis == 1) return;
if(x == b && y == a)
{
string s = Hash();
if(!mp[s])
{
mp[s] = true;
ans++;
}
}
return;
}
for(int i = 1; i <= 4; i++)
{
if(cnt[i] < 4)
{
cnt[i]++;
g[i][cnt[i]] = vis;
dfs(i, cnt[i], vis == 1 ? 2 : 1);
g[i][cnt[i]] = 0;
cnt[i]--;
}
}
}
int main()
{
scanf("%d%d%d", &x, &a, &b);
ans = 0;
cnt[x]++;
g[x][1] = 2;
dfs(x, 1, 1);
printf("%d\n", ans);
}
C - Game Map
给出一个图,寻找一条最长的路径,只能从度数小的点走到度数大的点。
根据原图的度可以建出DAG来,然后在dfs的过程中dp一下即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100050;
const ll mod = 1e9 + 7;
const ll INF = (1LL << 62) - 1;
int n, m, a, b;
int dis[maxn], no[maxn];
bool vis[maxn];
vector<int>maze[maxn];
void dfs(int u, int num)
{
if(dis[u] > num) return;
dis[u] = num;
int len = maze[u].size();
for(int i = 0;i < len;i++)
{
int v = maze[u][i];
if(vis[v] || no[v] <= no[u]) continue;
vis[v] = 1;
dfs(v, num+1);
vis[v] = 0;
}
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 0;i < m;i++)
{
scanf("%d%d", &a, &b);
maze[a].push_back(b);
maze[b].push_back(a);
no[a]++, no[b]++;
}
memset(dis, 0, sizeof(dis));
for(int i = 0;i < n;i++)
{
memset(vis, 0, sizeof(vis));
vis[i] = 1;
dfs(i, 1);
}
int ans = 0;
for(int i = 0;i < n;i++)
ans = max(ans, dis[i]);
printf("%d\n", ans);
return 0;
}
D - Happy Number
签到,直接模拟。
E - How Many to Be Happy?
给出一个图,对于每条边e定义H(e):最少从图中删去H(e)条边,才能将e加入到最小生成树中。求所有边H()的总和。
考虑kruskal求最小生成树的过程:对于两个端点不在同一集合的边,将这两个集合合并起来并将这条边加入最小生成树。
也就是说加入这条边的时候,它的两侧是不连通的。H(e)即为最少删掉多少条边,使得这条边的两个端点在没有这条边时不连通。
从小到大枚举所有的边,对于每一条边,在只保留权值严格小于它的边的情况下求两端点之间的最小割即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
const int maxm = 505;
const ll mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const double pi = acos(-1.0);
int n, m, no;
int head[maxn], level[maxn];
struct seg
{
int u, v;
int w;
}p[maxm];
bool cmp(seg a, seg b)
{
return a.w < b.w;
}
struct node
{
int to;
int nxt, flow;
}e[maxm << 2];
void add(int u, int v, int f)
{
e[no].to = v, e[no].nxt = head[u], e[no].flow = f;
head[u] = no++;
e[no].to = u, e[no].nxt = head[v], e[no].flow = 0;
head[v] = no++;
}
bool bfs(int s, int t)
{
memset(level, -1, sizeof(level));
level[s] = 0;
queue<int>q;
q.push(s);
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = head[u];i != -1;i = e[i].nxt)
{
int v = e[i].to;
if(level[v] == -1 && e[i].flow > 0)
{
level[v] = level[u] + 1;
q.push(v);
}
}
}
return (level[t] != -1);
}
int dfs(int s, int t, int f)
{
if(s == t) return f;
int tmp;
for(int i = head[s];i != -1;i = e[i].nxt)
{
int v = e[i].to;
if(level[v] == level[s] + 1 && e[i].flow > 0 && (tmp = dfs(v, t, min(f, e[i].flow))))
{
e[i].flow -= tmp;
e[i^1].flow += tmp;
return tmp;
}
}
return 0;
}
int dinic(int s, int t)
{
int res = 0;
while(bfs(s, t))
{
int tmp = dfs(s, t, INF);
res += tmp;
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 0;i < m;i++)
scanf("%d%d%d", &p[i].u, &p[i].v, &p[i].w);
sort(p, p+m, cmp);
int s = 0, t = n + 1, ans = 0;
for(int i = 1;i < m;i++)
{
no = 0;
memset(head, -1, sizeof(head));
for(int j = 0;j < i;j++)
{
if(p[j].w == p[i].w) break;
add(p[j].u, p[j].v, 1);
add(p[j].v, p[j].u, 1);
}
add(s, p[i].u, INF);
add(p[i].v, t, INF);
ans += dinic(s, t);
}
printf("%d\n", ans);
return 0;
}
F - Philosopher's Walk
哲♂学家以某种规则遍历n*n网格的每个位置,其中n是2的整数次幂,求某个时刻所在的位置。
通过观察,哲♂学家的路径是一个类似于分形的东西(大概?),然后在四个象限里面的图形都可以通过旋转得到,于是就递归地操作一下逐步缩小坐标范围就行了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100050;
const ll mod = 1e9 + 7;
const ll INF = (1LL << 62) - 1;
int n, m;
struct point
{
int x, y;
};
point solve(int n, int num)
{
point tmp;
if(n == 2)
{
if(num == 0) {tmp.x = 1, tmp.y = 1;}
else if(num == 1) {tmp.x = 1, tmp.y = 2;}
else if(num == 2) {tmp.x = 2, tmp.y = 2;}
else {tmp.x = 2, tmp.y = 1;}
return tmp;
}
int cnt = num/(n*n/4);
int no = num % (n*n/4);
tmp = solve(n/2, no);
if(cnt == 0) swap(tmp.x, tmp.y);
else if(cnt == 1) tmp.y += n/2;
else if(cnt == 2) {tmp.x += n/2, tmp.y += n/2;}
else
{
point res;
res.x = n+1-tmp.y;
res.y = 1-tmp.x+n/2;
tmp = res;
}
return tmp;
}
int main()
{
scanf("%d%d", &n, &m);
m--;
point tp = solve(n, m);
printf("%d %d\n", tp.x, tp.y);
return 0;
}
G - Rectilinear Regions
给出两条单调的折线,求围成封闭图形且红线在下的区域的总面积。
如果两条线单调不同,结果肯定是0。
将两条都递减的情况转化成都为递增的情况,然后将所有的点按照横坐标排序,从左到右扫一遍统计面积。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005;
const ll mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const double pi = acos(-1.0);
int n, m, N, no, pos[maxn];
struct node
{
int x, y;
int id;
}a[maxn], b[maxn], c[maxn];
bool cmp(node a, node b)
{
return a.x < b.x;
}
int main()
{
scanf("%d%d", &n, &m);
scanf("%d", &a[0].y), a[0].x = a[0].id = 0;
for(int i = 1;i <= n;i++)
scanf("%d%d", &a[i].x, &a[i].y), a[i].id = 0;
scanf("%d", &b[0].y), b[0].x = 0, b[0].id = 1;
for(int i = 1;i <= m;i++)
scanf("%d%d", &b[i].x, &b[i].y), b[i].id = 1;
n++, m++;
N = max(n, m);
int f1 = (a[1].y > a[0].y), f2 = (b[1].y > b[0].y);
if(f1 + f2 == 1) {puts("0 0"); return 0;}
else if(f1 + f2 == 0)
{
for(int i = 0;i < n;i++) a[i].y *= -1, a[i].id = 1;
for(int i = 0;i < m;i++) b[i].y *= -1, b[i].id = 0;
swap(a, b), swap(n, m);
}
no = 0;
for(int i = 1;i < n;i++) c[no++] = a[i];
for(int i = 1;i < m;i++) c[no++] = b[i];
sort(c, c+no, cmp);
int cnt = 0, lst = -1;
ll ans = 0, tmp = 0;
int ya = a[0].y, yb = b[0].y;
bool flag = (yb > ya);
for(int i = 0;i < no;i++)
{
if(c[i].id == 0)
{
if(lst != -1)
{
tmp += 1LL*(c[i].x - lst)*(yb - ya);
if(c[i].y < yb) lst = c[i].x;
else
{
lst = -1, cnt++;
ans += tmp, tmp = 0;
}
}
ya = c[i].y;
if(ya >= yb) flag = 0;
}
else
{
if(lst != -1)
{
tmp += 1LL*(c[i].x - lst)*(yb - ya);
lst = c[i].x;
}
yb = c[i].y;
if(!flag && lst == -1 && yb > ya)
lst = c[i].x;
}
}
printf("%d %I64d\n", cnt, ans);
return 0;
}
H - Rock Paper Scissors
剪刀石头布游戏,你和对手的出手序列都已经确定,找一个位置开始使得你赢的次数最多。
枚举三种手势,将你的序列中赢的置1,对手序列中输的置1,然后做一个卷积。三次卷积结果求和取最大的一个位置即可,用FFT加速。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 400050;
const ll mod = 1e9 + 7;
const ll INF = (1LL << 62) - 1;
const double pi = acos(-1.0);
int n, m, N, ans[maxn];
char s[maxn], t[maxn];
char mp[3][2] = {'S', 'P', 'R', 'S', 'P', 'R'};
struct Complex
{
double x, y;
Complex (double _x = 0.0, double _y = 0.0)
{
x = _x;
y = _y;
}
Complex operator + (const Complex &o) const
{
return Complex(x + o.x, y + o.y);
}
Complex operator - (const Complex &o) const
{
return Complex(x - o.x, y - o.y);
}
Complex operator * (const Complex &o) const
{
return Complex(x*o.x - y*o.y, x*o.y + y*o.x);
}
}a[maxn], b[maxn];
void change(Complex *s, int n)
{
int i, j, k;
for(i = 1, j = n/2;i < n-1;i++)
{
if(i < j) swap(s[i], s[j]);
k = n >> 1;
while(j >= k)
{
j -= k;
k >>= 1;
}
if(j < k) j += k;
}
}
void FFT(Complex *s, int n, int t)
{
change(s, n);
for(int h = 2;h <= n;h <<= 1)
{
Complex wn(cos(-t*2*pi/h), sin(-t*2*pi/h));
for(int j = 0;j < n;j += h)
{
Complex w(1, 0);
for(int k = j;k < j + (h>>1);k++)
{
Complex u = s[k], v = w*s[k+(h>>1)];
s[k] = u + v;
s[k+(h>>1)] = u - v;
w = wn*w;
}
}
}
}
int main()
{
scanf("%d%d%s%s", &n, &m, s, t);
N = 1;
while(N <= n + m) N <<= 1;
memset(ans, 0, sizeof(ans));
for(int o = 0;o < 3;o++)
{
for(int i = 0;i < N;i++)
{
if(i < n && s[n-i-1] == mp[o][1]) a[i].x = 1;
else a[i].x = 0;
if(i < m && t[i] == mp[o][0]) b[i].x = 1;
else b[i].x = 0;
a[i].y = b[i].y = 0;
}
FFT(a, N, 1);
FFT(b, N, 1);
for(int i = 0;i < N;i++) a[i] = a[i]*b[i];
FFT(a, N, -1);
for(int i = 0;i < N;i++) a[i].x /= N;
for(int i = 0;i < n;i++)
ans[i] += (int)(a[i].x + 0.5);
}
int res = 0;
for(int i = 0;i < N;i++)
res = max(res, ans[i]);
printf("%d\n", res);
return 0;
}
I - Slot Machines
给出一个数列的前n项,要是这个数列从第k项开始以p为周期,求p+k的最小值。
由于可能存在的循环节是在数列的尾部,于是将数列反过来处理出next数组,容易得出前k项的最小循环节长度是k-next[k]。然后遍历一遍所有的位置更新答案即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1000005;
const ll mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const double pi = acos(-1.0);
int n, a[maxn], nxt[maxn];
void getnxt()
{
int i = 1, j = 0;
nxt[1] = 0;
while(i <= n)
{
if(j == 0 || a[i] == a[j])
{
i++, j++;
nxt[i] = j;
}
else j = nxt[j];
}
}
int main()
{
scanf("%d", &n);
for(int i = n;i >= 1;i--)
{
scanf("%d", &a[i]);
}
getnxt();
int ansp = INF, ansk = INF;
for(int i = 2;i <= n+1;i++)
{
int p = i - nxt[i], k = n - i + 1;
if(p + k < ansp + ansk) ansp = p, ansk = k;
else if(p + k == ansp + ansk && p < ansp)
ansp = p, ansk = k;
}
printf("%d %d\n", ansk, ansp);
return 0;
}
J - Strongly Matchable
判断一个图是否为Strongly Matchable的,条件为对于任意n/2个边,都可以与剩下的n/2个边组成完美匹配。
留坑。交了10发随机都挂了……
K - Untangling Chain
给出一个点运动时拐弯的方向,要求设置每一步的长度,使得轨迹没有交叉的点。
构造。在每一步维护此时走到的x、y坐标的最大值和最小值,然后只要在这个方向上多走一个单位距离就好了(螺旋走位.jpg)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 10005;
const ll mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const double pi = acos(-1.0);
int n;
int num[maxn], dir[maxn];
int pos[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
int main()
{
scanf("%d", &n);
for(int i = 1;i <= n;i++)
scanf("%d%d", &num[i], &dir[i]);
int st = 0, nowx = 0, nowy = 0;
int maxx = 0, maxy = 0, minx = 0, miny = 0;
for(int i = 1;i <= n;i++)
{
if(st == 0)
{
num[i] = maxx - nowx + 1;
nowx += num[i];
maxx = max(maxx, nowx);
}
else if(st == 1)
{
num[i] = maxy - nowy + 1;
nowy += num[i];
maxy = max(maxy, nowy);
}
else if(st == 2)
{
num[i] = nowx - minx + 1;
nowx -= num[i];
minx = min(minx, nowx);
}
else if(st == 3)
{
num[i] = nowy - miny + 1;
nowy -= num[i];
miny = min(miny, nowy);
}
st = (st + dir[i] + 4) % 4;
}
for(int i = 1;i <= n;i++)
printf("%d%c", num[i], i==n ? '\n' : ' ');
return 0;
}
L - Vacation Plans
p个人同一天出发,在各自的图里要在同一天到达各自的机场,可以走路也可以躺尸,求最小的总代价。
dis[o][i][j]为第o个人在第i天到达点j的最小花费,跑若干遍dijkstra搞定……
但是不是很理解为什么处理1e5天都会wa……处理1.2e5天才ac
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 55;
const int maxm = 130050;
const ll INF = (1LL << 60) - 1;
int p, n, m, a, b, x[4];
ll dis[4][maxm][maxn], val[maxn], w;
vector<int>G[maxn];
vector<ll>W[maxn];
void sol(int o)
{
for(int i = 0;i < maxm;i++)
for(int j = 0;j < maxn;j++) dis[o][i][j] = INF;
dis[o][0][1] = 0;
for(int k = 0;k < maxm-1;k++)
{
for(int i = 1;i <= n;i++)
{
dis[o][k+1][i] = min(dis[o][k+1][i], dis[o][k][i] + val[i]);
for(int j = 0;j < G[i].size();j++)
{
int v = G[i][j];
dis[o][k+1][v] = min(dis[o][k+1][v], dis[o][k][i] + W[i][j]);
}
}
}
}
int main()
{
scanf("%d", &p);
for(int o = 1;o <= p;o++)
{
scanf("%d%d", &n, &m);
for(int i = 0;i < maxn;i++)
G[i].clear(), W[i].clear();
for(int i = 1;i <= n;i++) scanf("%I64d", &val[i]);
while(m--)
{
scanf("%d%d%I64d", &a, &b, &w);
G[a].push_back(b);
W[a].push_back(w);
}
scanf("%d", &x[o]);
sol(o);
}
ll ans = INF;
for(int i = 0;i < maxm;i++)
{
ll res = 0;
for(int j = 1;j <= p;j++)
res += dis[j][i][x[j]];
ans = min(ans, res);
}
printf("%I64d\n", ans);
return 0;
}