9.11 Test——NOI2011 Day2

T1:道路修建

【问题描述】

在 W 星球上有 n 个国家。为了各自国家的经济发展,他们决定在各个国家之间建设双向道路使得国家之间连通。但是每个国家的国王都很吝啬,他们只愿意修建恰好 n – 1 条双向道路。

每条道路的修建都要付出一定的费用,这个费用等于道路长度乘以道路两端的国家个数之差的绝对值。例如,在下图中,虚线所示道路两端分别有 2 个、4 个国家,如果该道路长度为 1,则费用为 1×|2 – 4|=2。图中圆圈里的数字表示国家的编号。

(图略)

由于国家的数量十分庞大,道路的建造方案有很多种,同时每种方案的修建费用难以用人工计算,国王们决定找人设计一个软件,对于给定的建造方案,计算出所需要的费用。请你帮助国王们设计一个这样的软件。

【输入格式】

从文件 road.in 中读入数据。

输入的第一行包含一个整数 n,表示 W 星球上的国家的数量,国家从 1 到 n编号。 接下来 n – 1 行描述道路建设情况,其中第 i 行包含三个整数 aibi ci,表示第 i 条双向道路修建在 ai bi 两个国家之间,长度为 ci

【输出格式】

输出到文件 road.out 中。

输出一个整数,表示修建所有道路所需要的总费用。

【样例输入】

6

1 2 1

1 3 1

1 4 2

6 3 1

5 2 1

【样例输出】

20

【数据规模与约定】

所有测试数据的范围和特点如下表所示

测试点编号

n 的规模(注意是等于号)

约定

1

n = 2

1≤ai, bin

0 ≤ci≤ 106

2

n = 10

3

n = 100

4

n = 200

5

n = 500

6

n = 600

7

n = 800

8

n = 1000

9

n = 10,000

10

n = 20,000

11

n = 50,000

12

n = 60,000

13

n = 80,000

14

n = 100,000

15

n = 600,000

16

n = 700,000

17

n = 800,000

18

n = 900,000

19

n = 1,000,000

20

n = 1,000,000

解析:

   开始时把题看错了以为要$LCT$,结果只需要把树建好,统计子树大小,每条边计算贡献即可,时间$O(n)$,(100分)

  $bzoj$上$dfs$好像过不了,需要把$dfs$改成$bfs$,然后就可过了,这里放$dfs$版的代码

 代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1000004;

inline int read()
{
    int ret, f=1;
    char c;
    while((c=getchar())&&(c<'0'||c>'9'))if(c=='-')f=-1;
    ret=c-'0';
    while((c=getchar())&&(c>='0'&&c<='9'))ret = (ret<<3)+(ret<<1)+c-'0';
    return ret*f;
}

int n, tot, head[maxn], siz[maxn];
ll ans;
struct edge{
    int nxt, to, d;
}e[maxn<<1];

void Addedge(int x, int y, int z)
{
    e[++tot] = (edge){head[x], y, z};
    head[x] = tot;
}

void dfs(int x, int fa)
{
    siz[x] = 1;
    for(int i = head[x]; i; i = e[i].nxt)
    {
        int id = e[i].to;
        if(id == fa)    continue;
        dfs(id, x);
        ans += ((siz[id]<<1) > n? (ll)((siz[id]<<1) - n) * (ll)e[i].d: (ll)(n - (siz[id]<<1)) * (ll)e[i].d);
        siz[x] += siz[id];    
    }
}

int main()
{
    freopen("road.in", "r", stdin);
    freopen("road.out", "w", stdout);
    n = read();
    for(int i = 1; i < n; ++i)
    {
        int x = read(), y = read(), z = read();
        Addedge(x, y, z);
        Addedge(y, x, z);
    }
    dfs(1, 0);
    printf("%lld
", ans);
    return 0;
}
road

T2:NOI嘉年华

【问题描述】

NOI2011 在吉林大学开始啦!为了迎接来自全国各地最优秀的信息学选手,吉林大学决定举办两场盛大的 NOI 嘉年华活动,分在两个不同的地点举办。每个嘉年华可能包含很多个活动,而每个活动只能在一个嘉年华中举办。

现在嘉年华活动的组织者小安一共收到了 n 个活动的举办申请,其中第 i 个活动的起始时间为 Si,活动的持续时间为 Ti。这些活动都可以安排到任意一个嘉年华的会场,也可以不安排。 小安通过广泛的调查发现,如果某个时刻,两个嘉年华会场同时有活动在进行(不包括活动的开始瞬间和结束瞬间),那么有的选手就会纠结于到底去哪个会场,从而变得不开心。所以,为了避免这样不开心的事情发生,小安要求不能有两个活动在两个会场同时进行(同一会场内的活动可以任意进行)。

另外,可以想象,如果某一个嘉年华会场的活动太少,那么这个嘉年华的吸引力就会不足,容易导致场面冷清。所以小安希望通过合理的安排,使得活动相对较少的嘉年华的活动数量最大。 此外,有一些活动非常有意义,小安希望能举办,他希望知道,如果第 i 个活动必须举办(可以安排在两场嘉年华中的任何一个),活动相对较少的嘉年华的活动数量的最大值。

【输入格式】

从文件 show.in 中读入数据。

输入的第一行包含一个整数 n,表示申请的活动个数。

接下来 n 行描述所有活动,其中第 i 行包含两个整数 SiTi,表示第 i 个活

动从时刻 Si 开始,持续 Ti 的时间。

【输出格式】

输出到文件 show.out 中。

输出的第一行包含一个整数,表示在没有任何限制的情况下,活动较少的嘉年华的活动数的最大值。 接下来 n 行每行一个整数,其中第 i 行的整数表示在必须选择第 i 个活动的前提下,活动较少的嘉年华的活动数的最大值。

【评分标准】

对于一个测试点:

l   如果输出格式不正确(比如输出不足 n+1 行),得 0 分; l 如果输出文件第一行不正确,而且后 n 行至少有一行不正确,得 0 分;

l   如果输出文件第一行正确,但后 n 行至少有一行不正确,得 4 分;

l   如果输出文件第一行不正确,但后 n 行均正确,得 6 分;

l   如果输出文件中的 n+1 行均正确,得 10 分。

【样例输入】

5

8 2

1 5

5 3

3 2

5 3

 

【样例输出】

2

2

1

2

2

2

【样例说明】

在没有任何限制的情况下,最优安排可以在一个嘉年华安排活动 1, 4,而在另一个嘉年华安排活动 3, 5,活动 2 不安排。

【数据规模与约定】

所有测试数据的范围和特点如下表所示

测试点编号

n 规模

约定

1

1≤n≤10

0≤Si≤109

1≤Ti≤ 109

2

1≤n≤40

3

4

1≤n≤200

5

6

7

8

9

10

解析:

   考场时大概猜到是$DP$,但太菜了想不到怎么$DP$,于是就只写了$10$分的暴力,最后结果还只有$4$分

  先把时间离散化,设总时间为$cnt$

  处理出$H[i][j]$,表示活动的开始与结束时间都在$[i, j]$中的活动数, $O(n^{2})$

  再处理出$f[i][j]$, 表示前$i$的时间内,一个分会场安排$j$个活动,另一个分会场最多安排多少活动, 注意这个有两个转移方式, 设$k$为之前的某个时间点,显然$H[k][i]$可以安排在分会场一或分会场二, 因此我们有:

  $f[i][j] = max(f[i][j], f[k][j] + H[k][i])$, $f[i][j] = max(f[i][j], f[k][j-H[k][i]])$

  $O(n^{3})$

  继续处理出$g[i][j]$,表示从$i$开始到最后的时间内,分会场一安排$j$个活动,分会场二最多安排多少活动, 转移方式与$f$数组类似

  $O(n^{3})$

  显然可以得到没有任何限制情况下的答案:

  $ans = maxleft { min(i, f[cnt][i]) ight } (0 leqslant i leqslant n)$

  $O(n)$

  麻烦就在于如何处理有限制条件下的答案

  再设一个$dp[i][j]$, 表示时间$[i, j]$内的活动必选的情况下,活动最少的分会场最多能安排多少活动,容易得到一个简单的转移方程:

  $dp[i][j] = maxleft { min(l+H[i][j]+r, f[i][l]+g[j][r]) ight }$

  $l$表示在前$i$的时间内安排$l$个活动,$r$表示在后$j$的时间内安排$r$个活动, 并且这$l$与$r$个活动与时间$[i, j]$内的活动安排在同一会场

  这个转移是$O(n^{4})$的, 显然会$T$

  考虑到对于任一$l$,随着的$l$增加,$f[i][l]$必然减少,$r$与$g[j][r]$也是同理。那么对于同一个$[i, j]$,随着$l$的增加,$r$一定比上一次在$l-1$时取的$r$小,因此可以把$r$改成一个从右向左移动的指针, 这样时间复杂度就降成了$O(n^{3})$,就可过了

  最后对于一个时间是$[l,r]$的活动, 它的答案应该是$maxleft { dp[i][j] ight }(1leqslant ileqslant l, rleqslant jleqslant cnt)$, 因为取$dp[l][r]$并不一定是最优的情况, 通过$dp$数组的转移方程就可以看出可能存在$dp[i][j] geqslant dp[l][r] (1leqslant ileqslant l, rleqslant jleqslant cnt)$

  细节还是看代码吧

 代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 505, inf = 0x3f3f3f3f;

int n, a[maxn], cnt;
int H[maxn][maxn], f[maxn][maxn], dp[maxn][maxn], g[maxn][maxn];
int ans[maxn];
struct seg{
    int l, r, id;
}s[maxn];

bool cmp(seg x, seg y)
{
    return x.r != y.r? x.r < y.r: x.l > y.l;
}

int main()
{
    freopen("show.in", "r", stdin);
    freopen("show.out", "w", stdout);
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        s[i].l = x;
        s[i].r = x + y;
        s[i].id = i;
        a[++cnt] = x;
        a[++cnt] = x + y;
    }
    sort(a + 1, a + cnt + 1);
    cnt = unique(a + 1, a + cnt + 1) - a - 1;
    sort(s + 1, s + n + 1, cmp);
    for(int i = 1; i <= n; ++i)
    {
        s[i].l = lower_bound(a + 1, a + cnt + 1, s[i].l) - a;
        s[i].r = lower_bound(a + 1, a + cnt + 1, s[i].r) - a;
    }
    for(int i = 1; i <= cnt; ++i)
        for(int j = 0; j <= n; ++j)
            f[i][j] = g[i][j] = -inf;
    int now = 1;    
    for(int i = 1; i <= cnt; ++i)
        for(int j = i - 1; j >= 1; --j)
        {
            H[j][i] = H[j][i - 1] + H[j+1][i] - H[j+1][i-1];
            while(s[now].r == i && s[now].l == j)
            {
                H[j][i] ++;
                now ++;
            }    
        }
    for(int i = 1; i <= cnt; ++i)
        for(int j = 0; j <= H[1][i]; ++j)
            if(j == 0)
                f[i][j] = H[1][i];
            else
                for(int k = 1; k < i; ++k)
                {
                    if(j <= H[1][k])
                        f[i][j] = max(f[i][j], f[k][j] + H[k][i]);
                    if(j >= H[k][i])
                        f[i][j] = max(f[i][j], f[k][j-H[k][i]]);
                }
    for(int i = cnt; i >= 1; --i)
        for(int j = 0; j <= H[i][cnt]; ++j)
            if(j == 0)
                g[i][j] = H[i][cnt];
            else
                for(int k = i + 1; k <= cnt; ++k)
                {
                    if(j <= H[k][cnt])    
                        g[i][j] = max(g[i][j], g[k][j] + H[i][k]);
                    if(j >= H[k][i])
                        g[i][j] = max(g[i][j], g[k][j-H[i][k]]); 
                }
    for(int i = 0; i <= n; ++i)
        ans[0] = max(ans[0], min(i, f[cnt][i]));
    for(int i = 1; i <= cnt; ++i)
        for(int j = i + 1; j <= cnt; ++j)
        {
            int l = 0, r = n, now, nxt;
            for(; l <= n; ++l)
            {
                for(; r; --r)
                {
                    now = min(l + r + H[i][j], f[i][l] + g[j][r]);
                    nxt = min(l + r - 1 + H[i][j], f[i][l] + g[j][r-1]);
                    if(nxt < now)
                        break;
                }    
                if(!r)
                    now = min(l + r + H[i][j], f[i][l] + g[j][r]);
                dp[i][j] = max(now, dp[i][j]);
            }    
        }
    for(int i = 1; i <= n; ++i)
    {
        for(int l = 1; l <= s[i].l; ++l)
            for(int r = s[i].r; r <= cnt; ++r)
                ans[s[i].id] = max(ans[s[i].id], dp[l][r]);
    }
    for(int i = 0; i <= n; ++i)
        printf("%d
", ans[i]);
    return 0;
}
show

T3:兔兔与蛋蛋游戏

【问题描述】

这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏。 这个游戏是在一个 n m 列的棋盘上进行的。游戏开始之前,棋盘上有一个格子是空的,其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色。

每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为:

l 兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格。 l 蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格。

第一个不能按照规则操作的人输掉游戏。为了描述方便,下面将操作“将第x 行第 y 列中的棋子移进空格中”记为 M(x,y)。

例如下面是三个游戏的例子。

(略)

最近兔兔总是输掉游戏,而且蛋蛋格外嚣张,于是兔兔想请她的好朋友——你——来帮助她。她带来了一局输给蛋蛋的游戏的实录,请你指出这一局游戏中所有她“犯错误”的地方。

注意:

l   两个格子相邻当且仅当它们有一条公共边。

l   兔兔的操作是“犯错误”的,当且仅当,在这次操作前兔兔有必胜策略,而这次操作后蛋蛋有必胜策略。

【输入格式】

从文件 game.in 中读入数据。 输入的第一行包含两个正整数 nm

接下来 n 行描述初始棋盘。其中第 i 行包含 m 个字符,每个字符都是大写英文字母"X"、大写英文字母"O"或点号"."之一,分别表示对应的棋盘格中有黑色棋子、有白色棋子和没有棋子。其中点号"."恰好出现一次。

接下来一行包含一个整数 k(1≤k≤1000),表示兔兔和蛋蛋各进行了 k 次操作。

接下来 2k 行描述一局游戏的过程。其中第 2i – 1 行是兔兔的第 i 次操作(编号为 i 的操作),第 2i 行是蛋蛋的第 i 次操作。每个操作使用两个整数 x,y 来描述,表示将第 x 行第 y 列中的棋子移进空格中。 输入保证整个棋盘中只有一个格子没有棋子,游戏过程中兔兔和蛋蛋的每个操作都是合法的,且最后蛋蛋获胜。

【输出格式】

输出到文件 game.out 中。

输出文件的第一行包含一个整数 r,表示兔兔犯错误的总次数。

接下来 r 行按递增的顺序给出兔兔“犯错误”的操作编号。其中第 i 行包含一个整数 ai 表示兔兔第 i 个犯错误的操作是他在游戏中的第 ai 次操作。

【输入样例 1】

1 6

XO.OXO

1

1 2

1 1

 

【输出样例 1】

1

1

 

【输入样例 2】

3 3

XOX

O.O XOX

4

2 3

1 3

1 2

1  1

2  1

3  1

3 2

3  3

【输出样例 2】

0

 

【输入样例 3】

4  4

OOXX

OXXO

OO.O

XXXO

2

3 2

2 2

1 2

1 3

【输出样例 3】

2

1

2

【样例说明】

样例 1 对应图一中的游戏过程。 样例 2 对应图三中的游戏过程。

 

【数据规模】

所有测试数据的范围和特点如下表所示

测试点编号

n 的规模

m 的规模

1

n = 1

1≤ m≤ 20

2

3

n = 3

m = 4

4

n = 4

m = 4

5

6

n = 4

m = 5

7

 8

n = 3

m = 7

n = 2

1 ≤m≤ 40

 9
10
11
12

13

14

15

1 ≤n≤ 16

1 ≤m≤ 16

16

17

1 ≤n≤ 40

1 ≤m≤ 40

18

19

20

 解析:

   博弈论转化为二分图

  首先是有一个性质,移动棋子的路径不会交叉

  把 . 看做X, 然后把X当做一边的点, 再把O看做另一边的点, 每个原图中的点向四周不同颜色的点连边
  然后又有一个性质,如果存在一个最大匹配不包含当前移动的点,当前移动的人必败, 否则必胜
  因此,我们可以判定兔兔操作之前,即蛋蛋上一次操作之后,兔兔是否是必胜态,也就是蛋蛋是否是必败态,再判定兔兔操作后是否是必败态,如果兔兔操作之前是必胜态,操作后变成了必败态,这就是犯错误的地方
 代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 50;

int n, m, sx, sy;
char s[maxn][maxn];

int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};

int head[maxn*maxn], tot;
struct edge{
    int nxt, to;
}e[maxn*maxn*4];

void Addedge(int x, int y)
{
    e[++tot] = (edge){head[x], y};
    head[x] = tot;
}

int mat[maxn*maxn], ban[maxn*maxn], vis[maxn*maxn], timer;
int ans[maxn*maxn], cnt;
bool flag[maxn*maxn];

bool dfs(int x)
{
    if(vis[x] == timer)    return 0;
    vis[x] = timer;
    for(int i = head[x]; i; i = e[i].nxt)
    {
        int id = e[i].to;
        if(ban[id] == 1)    continue;
        if(!mat[id] || dfs(mat[id]))
        {
            mat[id] = x;
            mat[x] = id;
            return 1;
        }
    }
    return 0;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i)
        scanf("%s", s[i] + 1);
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
        {
            if(s[i][j] == '.')
            {
                sx = i;
                sy = j;
            }
            if(s[i][j] == 'O')
                for(int k = 0; k < 4; ++k)
                    if(i + dx[k] >= 1 && i + dx[k] <= n && j + dy[k] >= 1 && j + dy[k] <= m && s[i][j] != s[i+dx[k]][j+dy[k]])
                    {
                        Addedge((i - 1) * m + j, (i + dx[k] - 1) * m + j + dy[k]);
                        Addedge((i + dx[k] - 1) * m + j + dy[k], (i - 1) * m + j);
                    }
        }
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
            if(!mat[(i - 1) * m + j])
            {
                ++timer;
                dfs((i - 1) * m + j);
            }
    int q;
    scanf("%d", &q);
    for(int i = 1; i <= (q<<1); ++i)
    {
        int x = (sx - 1) * m + sy;
        ban[x] = 1;
        if(mat[x])
        {
            int y = mat[x];
            mat[x] = mat[y] = 0;
            ++timer;
            flag[i] = !dfs(y);
        }
        scanf("%d%d", &sx, &sy);    
        if(!(i&1))
            if(flag[i-1] && flag[i])
                ans[++cnt] = (i>>1);
    }
    printf("%d
", cnt);
    for(int i = 1; i <= cnt; ++i)
        printf("%d
", ans[i]);
    return 0;
}
game
原文地址:https://www.cnblogs.com/Joker-Yza/p/11515082.html