2016-2017 National Taiwan University World Final Team Selection Contest

2016-2017 National Taiwan University World Final Team Selection Contest

A. Hacker Cups and Balls

題目描述:給定一個長度爲\(n\)的序列\(a\), 有\(m\)次操做,每次操做選擇一個區間\([L, R]\), 將區間內的數從小到大排序,或從大到小排序,問最終序列中間的數是什麼。c++

solution
二分答案\(mid\),將小於\(mid\)的數變成\(-1\),大於\(mid\)的數變成\(1\),等於\(mid\)的數爲0,每次操做至關於詢問某個區間內\(-1\)\(1\)的個數,而後將區間從新染色,這個能夠用線段樹維護。最終若是中間的數是\(0\),則二分的值就是答案,若是是\(-1\)說明答案偏大,不然答案偏小。ui

時間複雜度: \(O(nlog^2n)\)spa

B. Bored Dreamoon

題目描述:有\(n\)我的,他們的高度\(h_i\)兩兩不一樣,如今他們要排隊,有四個要求:rest

  1. 若是\(A, B\)不在同一行,則排在前面的人比後面的人矮。
  2. 若是\(A, B\)在同一行,則排在右邊的人比左邊的人矮。
  3. 任意兩行的人數的差不能大於\(1\)
  4. 前面行的人數要大於等於後面行的人數。
    定義\(A\)\(B\)的右前方:若是\(A, B\)不在同一行且在\(A\)右邊(同一行)的人數很少於在\(B\)右邊(同一行)的人數,或者\(A, B\)在同一行,且\(A\)\(B\)的右邊。

給定兩兩之間右前方的關係,問第一行人數的最小值,或者無解。code

solution
\(f[i]\)表示\(i\)這我的至少排在從右數起第\(f[i]\)個位置,根據題目給的條件能夠列出一些不等式,而後用差分約束求解。
但不太清楚爲何這樣能夠保證條件3,4.htm

時間複雜度:\(O(n^2)\)blog

C. Crazy Dreamoon

題目描述:有一個\(2000 \times 2000\)的網格圖,上面有一些線段,問這些線段穿過了多少個格子。排序

solution
枚舉\(x\)座標,根據線段定位\(y\)座標,染色,最後統計答案。隊列

時間複雜度:\(O(2000n)\)it

D. Forest Game

題目描述:給定一棵樹大小爲\(n\),你如今要將樹上的點所有刪掉,每次隨機選擇一個點,得分爲這個點所在的樹的大小,而後把這個點以及它所連的邊刪掉,求總得分的指望值乘\(n!\)

solution
考慮每一個點對總得分的貢獻,考慮兩個點\(u, v\),當選擇\(u\)刪掉時,\(v\)能對\(u\)的得分做出貢獻,當且僅當\(u\)\(v\)的路徑上,\(u\)是第一個被刪掉的點,這個機率爲(路徑長度+1)的倒數,這個機率等於\(v\)\(u\)的指望貢獻,所以問題轉化爲求每種路徑長度有多少條,總得分指望值就等於路徑條數/(路徑長度+1)的和。這個問題能夠用點分治+\(FFT\)解決。

時間複雜度:\(O(nlog^2n)\)

code

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

typedef long long LL;
const int mod=int(1e9)+7;
const int maxn=int(1e6)+100;

int n;
int deep[maxn], size[maxn];
int fa[maxn], maxs[maxn];
bool ban[maxn];
vector<int> pp, p;
vector<int> out[maxn];
int cnt[maxn];
LL ans[maxn], tmpans[maxn];
LL inv[maxn];

namespace FFT
{
    typedef complex<double> cplxd;
    const double PI=acos(-1);

    int n;
    int rev[maxn];
    cplxd ca[maxn], cb[maxn];

    void FFT_init()
    {
        for (int i=0; i<n; ++i) rev[i]=0;
        for (int i=0; i<n; ++i)
            for (int j=0; 1<<j<n; ++j)
                rev[i]=(rev[i]<<1) | (i>>j & 1);
    }

    void FFT(cplxd *a, int type)
    {
        for (int i=0; i<n; ++i)
            if (i<rev[i]) swap(a[i], a[rev[i]]);
        for (int i=2; i<=n; i<<=1)
        {
            cplxd wn(cos(2*PI/i), sin(type*2*PI/i));
            for (int j=0; j<n; j+=i)
            {
                cplxd w(1,0);
                for (int k=0; k<i>>1; ++k, w*=wn)
                {
                    cplxd tmp=a[j+k];
                    a[j+k]=a[j+k]+w*a[j+k+(i>>1)];
                    a[j+k+(i>>1)]=tmp-w*a[j+k+(i>>1)];
                }
            }
        }
    }

    void solve(int *a, int *b, LL *c, int nn)
    {
        nn<<=1;
        n=1;
        while (n<nn) n<<=1;
        FFT_init();
        for (int i=0; i<nn; ++i)
            ca[i]=cplxd(a[i], 0), cb[i]=cplxd(b[i], 0);

        for (int i=nn; i<n; ++i)
            ca[i]=cplxd(0, 0), cb[i]=cplxd(0, 0);

        FFT(ca, 1); FFT(cb, 1);
        for (int i=0; i<n; ++i) ca[i]=ca[i]*cb[i];
        FFT(ca, -1);
        for (int i=0; i<n; ++i) c[i]=LL(ca[i].real()/n+0.5)%mod;
    }
}

void read()
{
    scanf("%d", &n);
    for (int i=1; i<n; ++i)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        out[x].push_back(y);
        out[y].push_back(x);
    }
}
void dfs2(int cur, int _fa, int deep, int &maxdeep)
{
    maxdeep=max(maxdeep, deep);
    ++cnt[deep];
    for (auto &to:out[cur])
        if (to!=_fa && !ban[to]) dfs2(to, cur, deep+1, maxdeep);
}
int findroot(int cur, int _fa, int &total)
{
    int root=0;
    size[cur]=1;
    maxs[cur]=0;
    for (auto &to:out[cur])
        if (to!=_fa && !ban[to])
        {
            int nid=findroot(to, cur, total);
            if (root==0 || maxs[root]>maxs[nid]) root=nid;
            size[cur]+=size[to];
            maxs[cur]=max(maxs[cur], size[to]);
        }
    maxs[cur]=max(maxs[cur], total-size[cur]);
    if (root==0 || maxs[root]>maxs[cur]) root=cur;
    return root;
}
void dfs1(int root)
{
    int maxdeep=0;
    dfs2(root, 0, 0, maxdeep);
    FFT::solve(cnt, cnt, tmpans, maxdeep+1);
    for (int i=0; i<=maxdeep*2; ++i) ans[i]=(ans[i]+tmpans[i])%mod;
    for (int i=0; i<=maxdeep; ++i) cnt[i]=0;
    ban[root]=true;
    for (auto &to:out[root])
        if (!ban[to])
        {
            maxdeep=0;
            dfs2(to, 0, 1, maxdeep);
            FFT::solve(cnt, cnt, tmpans, maxdeep+1);
            for (int i=0; i<=maxdeep*2; ++i) ans[i]=(ans[i]-tmpans[i]+mod)%mod;
            for (int i=0; i<=maxdeep; ++i) cnt[i]=0;
        }
    for (auto &to:out[root])
        if (!ban[to]) { size[root]=size[to]; dfs1(findroot(to, 0, size[root])); }
}
void solve()
{ 
    dfs1(findroot(1, 0, n));
    LL answer=0;
    inv[1]=1;
    for (int i=2; i<=n; ++i) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
    for (int i=0; i<n; ++i) answer=(answer+ans[i]*inv[i+1]%mod)%mod;
    for (int i=1; i<=n; ++i) answer=answer*i%mod;
    printf("%lld\n", answer);
}
int main()
{   
    read();
    solve();
    return 0;
}

E. Lines Game

題目描述:給定一個序列\(p_i\),在二維平面上將\((0, i)\)\((1, p_i)\)相連,在給定一個費用\(c_i\),表示將\((0, i), (1, p_i)\)這條線段刪掉的費用,在刪掉一條線段的時候,與這條線段相交的線段都會被刪掉,但不須要費用,問最少須要多少花費才能把全部線段刪掉。

solution
將一條線段用一個點\((i, p_i)\)表示,則最終的方案必定是上升的,而且以相鄰兩個點做爲對角的矩形內部沒有點。設\(f[i]\)表示\(i\)必定要選,且\(i\)左下的點已經被刪掉的費用。cdq分治,左右兩邊按縱座標從小到大排序,分別維護兩個按橫座標排的單調棧,左邊的棧是用來維護能做爲決策的點,右邊是維護對應橫座標能在左邊選擇的高度範圍,而後用線段樹維護dp值。

時間複雜度:\(O(nlog^2n)\)

code

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

typedef long long LL;
const int maxn=int(1e5)+100;
const int inf=0x7fffffff;

struct mes
{
    int x, y, v;
};
struct data
{
    int value, cost, num;
};

int n;
data a[maxn];
int f[maxn], q[maxn], qq[maxn];
int g[maxn];
int tree[maxn*4];
mes dat;

void read()
{
    scanf("%d", &n);
    for (int i=1; i<=n; ++i) scanf("%d", &a[i].value);
    for (int i=1; i<=n; ++i) scanf("%d", &a[i].cost);
    for (int i=1; i<=n; ++i) a[i].num=i;
}
bool cmp0(data b, data c)
{
    return b.value<c.value;
}
bool cmp1(data b, data c)
{
    return b.num<c.num;
}
void build(int cur, int L, int R)
{
    tree[cur]=inf;
    if (L==R) return; 
    int mid=(L+R)>>1;
    build(cur<<1, L, mid);
    build(cur<<1 | 1, mid+1, R);
}
void updata(int cur, int L, int R)
{
    if (dat.x>R || dat.y<L) return;
    if (dat.x<=L && R<=dat.y)
    {
        tree[cur]=dat.v;
        return;
    }
    int mid=(L+R)>>1;
    updata(cur<<1, L, mid);
    updata(cur<<1 | 1, mid+1, R);
    tree[cur]=min(tree[cur<<1], tree[cur<<1 | 1]);
}
int ask(int cur, int L, int R)
{
    if (dat.x>R || dat.y<L) return inf;
    if (dat.x<=L && R<=dat.y) return tree[cur];
    int mid=(L+R)>>1;
    return min(ask(cur<<1, L, mid), ask(cur<<1 | 1, mid+1, R));
}
void cdq(int L, int R)
{
    if (L>=R)
    {
        if (f[L]<inf) f[L]+=a[L].cost;
        return;
    }
    int mid=(L+R)>>1;
    cdq(L, mid);
    sort(a+L, a+mid+1, cmp0);
    sort(a+mid+1, a+R+1, cmp0);
    int head=1, tail=0;
    int hh=1, tt=0;
    for (int i=mid+1, j=L; i<=R; ++i)
    {
        while (j<=mid && a[j].value<a[i].value)
        {
            dat.v=inf;
            while (head<=tail && a[q[tail]].num<a[j].num)
            {
                dat.x=dat.y=a[q[tail]].value; 
                updata(1, 0, n);
                --tail;
            }
            q[++tail]=j;
            dat.x=dat.y=a[j].value; dat.v=f[a[j].num];
            updata(1, 0, n);
            ++j;
        }
        while (hh<=tt && a[qq[tt]].num>a[i].num) --tt;
        qq[++tt]=i;
        dat.x=a[qq[tt-1]].value; dat.y=a[i].value;
        if (dat.x<=dat.y) f[a[i].num]=min(f[a[i].num], ask(1, 0, n));
    }
    dat.v=inf;
    while (tail)
    {
        dat.x=dat.y=a[q[tail]].value;
        updata(1, 0, n);
        --tail;
    }
    sort(a+mid+1, a+R+1, cmp1);
    cdq(mid+1, R);
}
void solve()
{
    f[0]=0;
    ++n;
    a[n].value=a[n].num=n;
    for (int i=1; i<=n; ++i) f[i]=inf;
    build(1, 0, n);
    cdq(0, n);
    printf("%d\n", f[n]);
}
int main()
{
    read();
    solve();
    return 0;
}

F. Lonely Dreamoon 2

題目描述:給定\(n\)個數,將這\(n\)個數從新排列,使得相鄰兩個數的差的絕對值的最小值最大。輸出方案。

solution
從小到大排序,結論是放在奇數位的數是連續的一段,所以只要找出連續的一段數,頭和尾的值相差最小,按順序放入奇數位,剩下的數從段尾+1開始,循環插入偶數位。

時間複雜度:\(O(n)\)

G. Dreamoon and NightMarket

題目描述:有\(n\)個數,找出全部子集中,和爲第\(k\)小的子集的和。

solution
從小到大排序,二分答案,用一個隊列記住當前的有效狀態,一個狀態包括當前的集合的和以及當前集合選擇的數的最大編號。每次從隊列裏面取出一個狀態,而後添加一個數,這個數的編號要大於最大編號,並且加上那個數的和要小於等於二分的答案,看總共有多少個集合的和小於等於二分的答案。

時間複雜度:\(O(nlogn)\)

H. Split Game

題目描述:給定一個在第一象限的簡單多邊形,用一條穿過原點的直線切割多邊形,問最多能切成多少個區域。

solution
將點按極角排序。假設如今直線通過點\(i\), 考慮八種狀況。

對於狀況2,4,6,8,區域沒有變化,
對於狀況1,當直線再偏一點時區域加一,對於狀況4,區域加一。
對於狀況3,區域減一,對於狀況7,當直線再偏一點時,區域減一。

根據不一樣狀況區域的數量變化,求出最大值。

時間複雜度:\(O(nlogn)\)

I. Tree Game

題目描述:給定一棵樹,開始時全部邊都是白色,每次操做選擇兩個葉子節點,這兩個葉子節點之間的路徑要全是白色,而後將路徑全塗成黑色,重複操做,直到沒法選擇爲止,問最少要多少次操做。

solution
貪心,每棵子樹只會向它的父親傳遞兩個葉子節點,對於一棵子樹,
若是有多於兩個兒子子樹爲\(1\),則多出來的子樹要兩兩匹配,
若是有多於一個兒子子樹爲\(2\),則多出來的子樹要兩兩匹配,
若是進行了前兩個操做以後,還有兩個子樹爲\(1\)的兒子,一個子樹爲\(2\)的兒子,則用一個\(1\)匹配一個\(2\)
最後想父親傳遞的葉子節點數位剩下的葉子節點數與\(2\)取最小值。

時間複雜度:\(O(n)\)

J. Zero Game

題目描述:給定一個\(01\)串,有一種操做:將某個位置的數移到某個位置。有若干個詢問,每次詢問爲一個數,問用這麼屢次操做獲得的串的最長\(0\)子串爲多長。

solution
若是移動的數字爲\(1\),則至關於將\(1\)直接移除,若是是\(0\),則至關於從某個地方移一個\(0\)過來。
先將連續的相同數字合併。設\(s1[i]\)\(1\)的個數前綴和,\(s0[i]\)\(0\)的個數前綴和。枚舉右端\(i\),要求出\(j\)使得\((s0[i]-s0[j])-(s1[i]-s1[j])\)最大,由於這至關於用\((s1[i]-s1[j])\)次操做得到了\((s0[i]-s0[j])\)個零,剩下的操做數直接從另外的地方移\(0\)過來。這裏能夠用單調隊列維護。

時間複雜度:\(O(Qn)\)

相關文章
相關標籤/搜索