【NOIP模擬】20151004模擬

題目

這裏看題面,密碼: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 long

const 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 long

const 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 lont

int 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 2147483647
using   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 long

const 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.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章