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

猜你喜欢

转载自blog.csdn.net/NPU_SXY/article/details/83046489