1. 程式人生 > >2018牛客暑期多校訓練賽 第一場 (A.D.E.J)

2018牛客暑期多校訓練賽 第一場 (A.D.E.J)

題目連結

A. Monotonic Matrix

給出n和m,問存在多少個n*m矩陣使得對於任意i, j都滿足a(i, j) <= a(i+1, j)且a(i, j) <= a(i, j+1),其中0 <= a(i, j) <= 2。

顯然一個位置的元素不大於右下方三個位置的元素。考慮0和1、1和2的分界線,題目就變成了:從(n, 0) 到(0, m)的兩條不相交可重合路線有多少種方案。

考慮將其中一條路徑平移,就轉化為不可重疊的方案數。計算從(0, 0)到(n, m)的方案數時,考慮一條從(1, 0)到(n, m-1),另一條從(0, 1)到(n-1, m)。二者乘起來再減去相交的,即減去從(0, 1)到(n, m-1)與(1, 0)到(n-1, m)的方案數。

實際上就是套Lindström–Gessel–Viennot定理,答案為(C_{n+m}^{m})^{2}-C_{n+m}^{n-1}*C_{n+m}^{m-1}

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int maxn = 2050;
const ll mod = 1e9 + 7;

int n, m;
ll C[maxn][maxn];

void init()
{
    for(int i = 0;i <= 2000;i++) C[i][i] = C[i][0] = 1;
    for(int i = 2;i <= 2000;i++)
    {
        for(int j = 1;j < i;j++)
            C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;
    }
}

int main()
{
    init();
    while(~scanf("%d%d", &n, &m))
    {
        ll ans = ((C[n+m][m]*C[n+m][m] % mod) - (C[n+m][m-1]*C[n+m][n-1] % mod) + mod) % mod;
        printf("%lld\n", ans);
    } 
}

D. Two-Graghs

給出兩個圖G1、G2,問G2的子圖中有多少個與G1同構。

O(n!)列舉所有與G1同構的圖,然後判斷是否是G2的子圖即可。在判斷的時候,由於最多隻有28條邊,可以用位運算壓到一個int裡面來記錄邊的組合方案是否重複。總的時間複雜度為O(n!n^{2})

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int maxn = 12;

int n, ma, mb, u, v, a[maxn];
int ga[maxn][maxn], gb[maxn][maxn];
map<int, int>mp;

int main()
{
    while(scanf("%d%d%d", &n, &ma, &mb) != EOF)
    {
        mp.clear();
        memset(ga, 0, sizeof(ga));
        memset(gb, 0, sizeof(gb));
        for(int i = 1;i <= ma;i++)
        {
            scanf("%d%d", &u, &v);
            ga[u][v] = ga[v][u] = 1;
        }
        for(int i = 1;i <= mb;i++)
        {
            scanf("%d%d", &u, &v);
            gb[u][v] = gb[v][u] = i;
        }
        for(int i = 1;i <= n;i++) a[i] = i;
        int ans = 0;
        do
        {
            int flag = 1, tmp = 0;
            for(int i = 1;i <= n;i++)
            {
                for(int j = 1;j <= n;j++)
                {
                    if(!ga[i][j]) continue;
                    if(!gb[a[i]][a[j]]) {flag = 0; break;}
                    tmp |= (1 << gb[a[i]][a[j]]);
                }
                if(!flag) break;
            }
            if(flag && !mp[tmp]) {mp[tmp] = 1; ans++;}
        }while(next_permutation(a+1, a+n+1));
        printf("%d\n", ans);
    }
    return 0;
}

E. Removal

給出一個長度為n的陣列,要求去掉m個元素,問能得到多少個不同的新陣列。

實際上就是問長度為n-m的不同的子序列有多少。考慮先預處理出nxt[i][x]表示在位置i,數字x下一次出現的位置。用dp[i][j]表示前i個位置去掉了j個的方案數,在每一個位置上都列舉去掉的數目與處理的下個位置,顯然有:

dp[nxt(i, p)][nxt(i, p)+j-i-1] += dp[i][j]。

最後的答案是\sum_{i=0}^{m}dp[n-i][m-i]

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int maxn = 100050;
const ll mod = 1e9 + 7;

int n, m, k, a[maxn];
int nxt[maxn][12];
ll dp[maxn][12];

int main()
{
    while(~scanf("%d%d%d", &n, &m, &k))
    {
        for(int i = 1;i <= n;i++) scanf("%d", &a[i]);
        for(int i = 1;i <= k;i++) nxt[n][i] = n + 1;
        for(int i = n;i >= 1;i--)
        {
            for(int j = 1;j <= k;j++)
                nxt[i-1][j] = nxt[i][j];
            nxt[i-1][a[i]] = i;
        }
        memset(dp, 0, sizeof(dp));
        dp[0][0] = 1;
        for(int i = 0;i < n;i++)
        {
            for(int j = 0;j <= m;j++)
            {
                for(int p = 1;p <= k;p++)
                {
                    int tmp = nxt[i][p], tp = j+tmp-i-1;
                    if(tp <= m)
                        dp[tmp][tp] = (dp[tmp][tp] + dp[i][j]) % mod;
                }
            }
        }
        ll ans = 0;
        for(int i = 0;i <= m;i++)
            ans = (ans + dp[n-i][m-i] + mod) % mod;
        printf("%lld\n", ans);
    }
    return 0;
}

J. Different Integers

給出一個數列,要求q次查詢,每次給出l, r,求[1, l] 和 [r, n]中不同元素的個數。

首先考慮如何求一個區間[l, r]中不同元素的個數。這個東西可以用樹狀陣列來維護,從前往後遍歷這個數列,對於一個元素a[i],在i這個位置上加1,同時如果a[i]在前面出現過,那麼就在上一次出現的位置上減1。出現的不同元素個數就是維護的這個樹狀陣列的區間和。

需要注意的是樹狀陣列的這個解法需要將所有查詢按照右端點排序然後離線解決。

而這個問題中,[1, l]和[r, n]區間上的答案實際上就是[r, n+l]上的答案,只需要將原數列倍增一遍即可。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int maxn = 200050;

int n, q, a[maxn], vis[maxn];
int tree[maxn], ans[maxn];
struct node
{
    int l, r;
    int id;
    bool operator < (const node &o) const
    {
        return r < o.r;
    }
}e[maxn];
int lowbit(int x) {return x & (-x);}

void add(int x, int w)
{
    while(x < maxn)
    {
        tree[x] += w;
        x += lowbit(x);
    }
}

int query(int x)
{
    int res = 0;
    while(x)
    {
        res += tree[x];
        x -= lowbit(x);
    }
    return res;
}

int main()
{
    while(scanf("%d%d", &n, &q) != EOF)
    {
        for(int i = 1;i <= n;i++)
        {
            scanf("%d", &a[i]);
            a[i+n] = a[i];
        }
        memset(tree, 0, sizeof(tree));
        memset(vis, 0, sizeof(vis));
        for(int i = 1;i <= q;i++)
        {
            scanf("%d%d", &e[i].l, &e[i].r);
            e[i].l += n;
            swap(e[i].l, e[i].r);
            e[i].id = i;
        }
        sort(e+1, e+q+1);
        int pos = 1;
        for(int i = 1;i <= n*2;i++)
        {
            if(vis[a[i]]) add(vis[a[i]], -1);
            vis[a[i]] = i;
            add(vis[a[i]], 1);
            while(e[pos].r <= i && pos <= q)
            {
                ans[e[pos].id] = query(e[pos].r) - query(e[pos].l - 1);
                pos++;
            }
        }
        for(int i = 1;i <= q;i++)
            printf("%d\n", ans[i]);
    }
    return 0;
}