1. 程式人生 > >2017-2018 ACM-ICPC, Asia Daejeon Regional Contest 【11/12】

2017-2018 ACM-ICPC, Asia Daejeon Regional Contest 【11/12】

題目連結

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;
}