【NOIP模拟】20151004模拟

来源:互联网 发布:织梦软件频道 编辑:程序博客网 时间:2024/04/30 00:18

题目

点这里看题面,密码:6wfu
点这里看出题人题解,密码:5uhh
丧心病狂的你想看我们的分数的话就戳这里
Johann是蒟蒻,Orz各位神犇

T1 NUMBER

1、 因为 4*7+3=8*3+7,所以这题目给出的两种操作是不受先后顺序影响的。
2、 (4x + 3) 3 = (8x + 7)2 ,所以 (4x + 3) 的操作次数不会超过 2 次。
3、如果你像我一样聪(NaoDong)明(Da),你会发现4x+3=(2x+1)2,8x+3=(2x+1)3
4、 题目保证答案不超过 100000。
所以枚举(2x+1)的次数,优先转化成(8x+7)就可以了
如果没有发现3,也可以优先枚举(8x+7),稍微繁琐一些

#include <bits/stdc++.h>using namespace std;#define rep(i, a, b) for(int i = (a); i <= (b); i++)#define red(i, a, b) for(int i = (a); i >= (b); i--)#define ll long longconst ll mod = 1000000007ll;ll n;int main() {    freopen("number.in", "r", stdin);    freopen("number.out", "w", stdout);    scanf("%I64d", &n);    n %= mod;    int cnt = 0;    while(n != 0) {        n = (n << 1ll) + 1ll; n %= mod;        cnt++;    }    if (cnt % 3 == 0) cnt /= 3;    else if (cnt % 3 == 1) cnt = (cnt - 4) / 3 + 2;    else cnt = cnt / 3 + 1;    printf("%d\n", cnt);    return 0;}

T2 TRAVEL

一开始GJ大神说要Tarjan,我受到了惊吓
先用Floyd把新图建出来,对每一条边打好标记
然后spfa,用d[u][k]记录状态,u表示当前节点,k表示“路径2”走了k次
统计答案

#include <bits/stdc++.h>using namespace std;#define rep(i, a, b) for(int i = (a); i <= (b); i++)#define red(i, a, b) for(int i = (a); i >= (b); i--)#define ll long longconst int inf = 1000000000;const int maxn = 200, maxm = 20000;struct edge{    int from, to, len, tag, nxt;}e[maxm];struct node{    int pos, step;};int G[maxn][maxn], g[maxn][maxn], d[maxn][20], inq[maxn][20];int head[maxn];int n, m, lim, tail = 0;inline void setIO() {    freopen("travel.in", "r", stdin);    freopen("travel.out", "w", stdout);}void init() {    scanf("%d%d%d", &n, &m, &lim);    rep(i, 1, n)        rep(j, 1, n)            g[i][j] = i == j ? 0 : inf;    memset(e, 0, sizeof(e));    rep(i, 1, n) head[i] = -1;    rep(i, 1, m) {        int x, y, z;        scanf("%d%d%d", &x, &y, &z);        g[x][y] = min(g[x][y], z);        G[x][y] = g[x][y];    }       }void make_graph() {    rep(k, 1, n) {        rep(i, 1, n) {            rep(j, 1, n) {                if (g[i][k] + g[k][j] < g[i][j])                     g[i][j] = g[i][k] + g[k][j];            }        }    }    rep(i, 1, n) {        rep(j, 1, n) {            if (i == j) continue;            if (G[i][j] == 0) continue;            if (g[i][j] != inf && g[j][i] != inf) {                e[++tail].from = i;                e[tail].to = j;                e[tail].len = G[i][j];                e[tail].tag = 0;                e[tail].nxt = head[i];                head[i] = tail;            }else {                e[++tail].from = i;                e[tail].to = j;                e[tail].len = G[i][j] * 2;                e[tail].tag = 1;                e[tail].nxt = head[i];                head[i] = tail;            }        }    }}void spfa(int s, int t) {    rep(i, 1, n)        rep(j, 0, lim)             d[i][j] = inf;    d[s][0] = 0;    rep(i, 1, lim) d[s][i] = inf;    memset(inq, 0, sizeof(inq));    queue<node> q;    q.push((node){s, 0});    inq[s][0] = 1;    while(!q.empty()) {        node point = q.front();        int now = point.pos, k = point.step;        q.pop();        inq[now][k] = 0;        for(int i = head[now]; i != -1; i = e[i].nxt) {            int v = e[i].to;            if (d[now][k] + e[i].len < d[v][k + e[i].tag] && k + e[i].tag <= lim) {                d[v][k + e[i].tag] = d[now][k] + e[i].len;                if (!inq[v][k + e[i].tag]) {                    inq[v][k + e[i].tag] = 1;                    q.push((node){v, k + e[i].tag});                }             }        }    }}void print() {    int ans = inf;    rep(i, 0, lim) ans = min(ans, d[n][i]);    if (ans == inf) printf("-1\n");    else printf("%d\n", ans); }int main() {    setIO();    init();    make_graph();    spfa(1, n);    print();    return 0;}

T3 BRICK

做法和STD有一些不同,先说我的
先想出%60的dp,用f[i][j][l][r]记录状态(第i行取l到r,空j格),在枚举ll和rr,从i-1行的ll到rr转移过来,n4状态,n6转移
由于转移是迭代的,所以状态可以省去第一维,但没有优化时间
考虑优化转移。ll和rr同时满足 ll <= r 且 rr >= l, 这个条件即表示ll为行,rr为列的矩阵中,(r,l)这个点右上方所有值,而转移就是要求这些值中的最大值。不难发现,这个“矩阵”就是第i-1行的决策表,需要注意的是,这个决策表对每一个j(空j格)都要记录决策表g
这样就只需要两个n3的数组,当f计算完后就更新g,能做到n4转移

#include <bits/stdc++.h>using namespace std;#define rep(i, a, b) for(int i = (a); i <= (b); i++)#define red(i, a, b) for(int i = (a); i >= (b); i--)#define ll long lontint n, m, k;int h[100][100][100], g[100][100][100];int sum[100][100], a[100][100];int main() {    freopen("brick.in", "r", stdin);    freopen("brick.out", "w", stdout);    scanf("%d%d%d", &n, &m, &k);    k = n * m - k;    rep(i, 1, n)        rep(j, 1, m)            scanf("%d", &a[i][j]);    rep(i, 1, n) {        sum[i][0] = 0;        rep(j, 1, m) sum[i][j] = sum[i][j - 1] + a[i][j];    }    memset(g, 0, sizeof(g));// maxnum of a matrix    memset(h, 0, sizeof(h));// ans that is being calculated    rep(i, 1, n) {        rep(j, 1, k) {            rep(l0, 1, m) {                rep(r0, l0, m) {                    int len = r0 - l0 + 1;                    if (len > j) break;                    h[j][l0][r0] = g[j - len][r0][l0] + sum[i][r0] - sum[i][l0 - 1];                }            }        }        rep(j, 1, k) {            rep(l, 1, m) {                red(r, m, 1) {                    g[j][l][r] = h[j][l][r];                    g[j][l][r] = max(g[j][l][r], g[j][l][r + 1]);                    g[j][l][r] = max(g[j][l][r], g[j][l - 1][r]);                }            }        }    }    int ans = 0;    rep(l0, 1, m)        rep(r0, l0, m)            ans = max(ans, h[k][l0][r0]);    printf("%d\n", ans);    return 0;}

题解提供了一种不同的做法,在这里也贴出
1、 由于max⁡(nm − 64,0) ≤ k < nm,所以保留的格子数不会超过 64 个。
2、 设 left[i][j][k]表示从(i,j)格子开始,只允许向左走或者向上走,总共走k步可以得到的
最优值,设 right[i][j][k]表示从(i,j)格子开始,只允许向右走或者向上走,总共走k步
可以得到的最优值。这样表示了状态后,问题的答案可以通过枚举底行遍历的起始
位置 x 列,然后取 left[n][x][nm-k]和 right[n][x][nm-k]的最大值即可。
3、 关于转移,对于 left[i][j][k]状态有两类转移方式:第一种情况,是在当前行向左走一
步,状态转移到左边相邻的格子。第二种情况,left 状态如果不向左扩展,就只能向
上扩展。对于最优解来自向上扩展的状态,根据连续性的特点,我们可以发现这类
状态有这样一个特点:或者(i,j)是方案中第 i 行的一个端点(左端点获右端点),或者
它向上一行到达的格子(i-1,j)是对应行的一个端点。
具体转移要分三种情况:
(1),向上走一行到达的格子(i-1,j)是对应行的左端点;此时向上扩展后不能再向左扩
展,该情况如图(a)所示,黑色格子为当前位置。由于(i,j)不一定是当前的左端点,因
此需要枚举当前位置左边还需要取的一段格子的长度 x,对应图(a)中的灰色段,然
后向 right[i-1][j][k-x-1]转移。
(2),向上走一行到达的格子(i-1,j)是对应行的右端点;此时向上扩展后不能再向右扩
展,该情况如图(b)所示,黑色格子为当前位置。由于(i,j)不一定是当前的左端点,因
此需要枚举当前位置左边还需要取的一段格子的长度 x,对应图(b)中灰色段,然后
向 left[i-1][j][k-x-1]转移。
(3),当前位置是当前行的一个左端点;此时,向上扩展会有左右两个方向可以选择,
如图(c)和(d)所示。与前面两种情况相似,由于上升一行后不是端点位置,在左右某
个方向转移的同时,需要枚举另一个方向需要取的格子的长度,对应图(c)和(d)中的
灰色段。
T3示意图
状态 right[i][j][k]的转移方法跟 left[i][j][k]的转移方法类似,这里不再阐述。根据前面分析的
状态转移方法,向左或向右转移的时间复杂度为 O(1),而向上转移的时候,需要枚举某个灰
色段的长度,这个计算量为 O(N)。所以该算法总的时间复杂度为O(NMK 2 ),这里的 K 表示要
保留的格子个数K ≤ 64。

#include <iostream>#include <cstdio>#define maxn 65#define maxlongint 2147483647using   namespace std;int     n,m,kth,ans;int     a[maxn][maxn],sum[maxn][maxn],Left[maxn][maxn][maxn],Right[maxn][maxn][maxn];void    init(){        cin>>n>>m>>kth;        kth=n*m-kth;        for (int i=1; i<=n; i++)                for (int j=1; j<=m; j++)                {                        cin>>a[i][j];                        sum[i][j]=sum[i][j-1]+a[i][j];                }}void    slove(){        for (int k=1; k<=kth; k++)                for (int i=1; i<=n; i++)                        for (int j=1; j<=m; j++)                        {                                int &p=Left[k][i][j];                                int &q=Right[k][i][j];                                int tmp;                                if (k==1)                                {                                        p=q=a[i][j];                                        continue;                                }                                p=q=-maxlongint;                                if (j>1) p=Left[k-1][i][j-1]+a[i][j];                                if (j<m) q=Right[k-1][i][j+1]+a[i][j];                                if (i==1) continue;                                for (int x=0; x<k; x++)                                {                                        int remain=k-x-1;                                        if (x<j) p=max(p,sum[i][j]-sum[i][j-1-x]+max(Right[remain][i-1][j],Left[remain][i-1][j]));                                        if (x+j<=m) q=max(q,sum[i][j+x]-sum[i][j-1]+max(Right[remain][i-1][j],Left[remain][i-1][j]));                                        if (x<j && x<k-1)                                        {                                                tmp=Right[remain][i-1][j]+a[i][j]+sum[i-1][j-1]-sum[i-1][j-1-x];                                                p=max(p,tmp);                                                q=max(q,tmp);                                        }                                        if (x+j<=m && x<k-1)                                        {                                                tmp=Left[remain][i-1][j]+a[i][j]+sum[i-1][j+x]-sum[i-1][j];                                                p=max(p,tmp);                                                q=max(q,tmp);                                        }                                }                        }        ans=0;        for (int i=1; i<=m; i++) ans=max(ans,max(Left[kth][n][i],Right[kth][n][i]));        cout<<ans<<endl;}int     main(){      freopen("brick.in","r",stdin);      freopen("brick.out","w",stdout);        init();        slove();}

孰优孰劣,仁者见仁智者见智

T4 MAXIMIZE

1、 二分图匹配的模型是容易想到的。构造一个二分图 G(X,Y),X 集中的每个顶点对应原
图的每个顶点,Y 集中的每个顶点对应原图中每一条边。若原图存在一条边可以赋
值给某个顶点,那么在图 G 中相应顶点中连边,边权是原图中的那条边的权值。但
对于这个题来说,这样显然时间复杂度过高。
2、 原图中所有边的权值最大者,一定存在于最优分配方案中,否则我们可以把它分配
给一个顶点,使得解更优。对于原图中权值第二大的边,如果在不影响权值最大的
边的分配,则这条边也存在于最优解中。以此类推,我们按照权值从大到小排序原
图的边,然后以此判断每条边的加入是否影响比它大的边的分配,如果不影响,则
选取,否则弃之。
现在的问题是怎么判断一条边的加入是否影响比它大的边的分配。
1、 考虑一个联通块,对于本题,在谈论混合图的连通性时,只考虑它的无向边,而忽
略它的有向边。
2、 如果满足上述条件的分配方案是存在的,那么对于图上的每个连通块,顶点的数量
一定不少于边的数量。
3、 其实,相反,若图上每个连通块的顶点数量都不少于边的数量,那么是否存在一个
满足上述条件的方案?答案是肯定的。若在一个连通块上,有一条边未被分配,那
么一定有另外一个顶点未分配权值。既然图是连通的,那么这条边和顶点之间就有
一条路径。可以沿着这条路径修改分配方案,使得这条边被分配而不影响其余分配
的边。所以最后问题转化为统计一个连通块的顶点数和边数之差的问题。
4、 这个问题可以用并查集解决

#include <bits/stdc++.h>using namespace std;#define rep(i, a, b) for(int i = (a); i <= (b); i++)#define red(i, a, b) for(int i = (a); i >= (b); i--)#define ll long longconst int maxn = 1500, maxm = maxn * (maxn - 1) / 2;struct node{    int from, to, tag, len;}e[maxm];int fa[maxn], delta[maxn];int n, m, ans = 0;bool cmp(node a, node b) { return a.len > b.len; }int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }int main() {    freopen("maximize.in", "r", stdin);    freopen("maximize.out", "w", stdout);    scanf("%d%d", &n, &m);    rep(i, 1, m) scanf("%d%d%d%d", &e[i].from, &e[i].to, &e[i].tag, &e[i].len);    sort(e + 1, e + m + 1, cmp);    rep(i, 1, n) fa[i] = i, delta[i] = 1;    rep(i, 1, m) {        if (e[i].tag == 0) {            int x = find(e[i].from);            int y = find(e[i].to);            if (x != y) {                if (delta[x] + delta[y] > 0) {                    ans += e[i].len;                    delta[x] += delta[y] - 1;                    fa[y] = x;                }            } else {                if (delta[x] > 0) {                    ans += e[i].len;                    delta[x]--;                }            }        } else{            int x = find(e[i].from);            if (delta[x] > 0) {                ans += e[i].len;                delta[x]--;            }        }    }    printf("%d\n", ans);    return 0;}

尾声

的确好久没有做题了,非常愧疚呢~
这套题前三题都是做出来的,最后一题来不及想,写了暴力。结束之后也做出来了,感觉题目出的还是挺合理,四道题有点不可理喻TAT
数据出的简直goushi,在testdata里面“打草稿”!
其实这是一套陈题,2012年的时候的训练题-_-||
GJ和QY神犇都做过的哇咔咔……难怪做的那么快的说
好像没有什么可以吐槽的了
哦对,还有SXP同学的四个妹子

End.

0 0