【弱校胡策】2016.4.14 (bzoj2164)最短路+狀壓DP+矩陣乘法+高斯消元+樹鏈剖分+線段樹+揹包DP

cyyz&qhyz&lwyz&gryz弱校胡策 命題人:cyyz ws_fqk

T3暴力寫挫了 50+10+0滾粗辣!


奇妙的約會(appointment.cpp/c/pas)

【問題描述】

DQS和sxb在網上結識後成爲了非常好的朋友,並且都有着驚人
的OI水平。在NOI2333的比賽中,兩人均拿到了金牌,並保送進入
HU/PKU。於是兩人決定在這喜大普奔的時刻進行面基。
NOI2333參賽選手衆多,所以安排了n個考點,DQS在1號考點,
而sxb在n號考點。由於是舉辦全國性賽事的城市,自然有許多奇妙
的性質:
某些考點會組成一個集合, 集合中的考點兩兩之間的距離爲一固
定值,且一個考點可以屬於多個集合。
形式的說:有n個考點,m個集合,集合i中包含s_i個考點,
且這s_i個考點之間兩兩距離爲t_i。
金牌爺的時間都非常珍貴,現在他們想知道,兩人同時出發,他
們到達同一個考點見面所需的最短時間是多少?並且他們想知道, 滿
足時間最短的考點分別是哪些?

【輸入】

第一行包含兩個整數n,m,意義如題意所說。
接下來m行,首先每行會有兩個整數,分別代表t_i,s_i,接下
來包含s_i個整數,表示集合i中所含的考點編號。

【輸出】

若兩人無法見面,則輸出“impossible”。
否則,第一行輸出一個整數,表示最短時間。
第二行輸出滿足時間最短的考點編號,相鄰的用空格隔開。

【樣例輸入】

5 4
1 3 1 2 3
2 2 3 4
10 2 1 5
3 3 3 4 5

【樣例輸出】

3
3 4

【數據範圍】

對於50%的數據
N<=1000,sigma(s_i)<=1000
對於100%的數據
2<=n<=10^5,1<=t_i<=10^9,s_i>0,sigma(s_i)<=10^6


n^2建邊 50分

%學娣 @Loi_a 的玄學暴力在開O2O3的前提下A掉此題…

可以發現每個集合並不需要建這麼多邊。新建兩個點表示次集合的入點、出點,然後集合中每個點向入點建權值爲0的邊,出點向每個點建權值爲0的邊,入點向出點建權值爲t的邊,跑兩遍最短路即可。

據說原題是doc出的題?

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

typedef long long LL; 
const int SZ = 3000010;
const int INF = 1000000010;

int head[SZ],nxt[SZ];

struct edge{
    int t;
    LL d;
}l[SZ];

void build(int f,int t,LL d)
{
    static int tot = 1;
    l[++ tot].t = t;
    l[tot].d = d;
    nxt[tot] = head[f];
    head[f] = tot;
}

LL dist1[SZ],dist2[SZ];

struct Heap{
    int u;
    LL d;
    Heap(int a = 0,LL b = 0) : u(a),d(b) {}
};

bool operator <(Heap a,Heap b)
{
    return a.d > b.d;
}

priority_queue<Heap> q;

bool vis[SZ];

void spfa(int s)
{
    memset(dist2,63,sizeof(dist2));
    memset(vis,0,sizeof(vis));
    dist2[s] = 0;
    q.push(Heap(s,0));
    while(q.size())
    {
        int u = q.top().u; q.pop();
        if(vis[u]) continue;
        vis[u] = 1; 
        for(int i = head[u];i;i = nxt[i])
        {
            int v = l[i].t;
            if(dist2[v] > dist2[u] + l[i].d)
            {
                dist2[v] = dist2[u] + l[i].d;
                q.push(Heap(v,dist2[v]));
            }
        }
    }
}

int main()
{
    freopen("appointment.in","r",stdin);
    freopen("appointment.out","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= m;i ++)
    {
        int t,s;
        scanf("%d%d",&t,&s);
        int in = i + 100000,out = i + 100000 * 2;
        build(in,out,t);
        while(s --)
        {
            int x; scanf("%d",&x);
            build(x,in,0); build(out,x,0);
        }
    }
    spfa(1); 
    for(int i = 1;i <= n;i ++)
        dist1[i] = dist2[i];
    spfa(n);

    LL maxd = INF;
    for(int i = 1;i <= n;i ++)
        maxd = min(maxd,max(dist1[i],dist2[i]));
    if(maxd >= INF)
        { puts("impossible"); return 0; }
    printf("%lld\n",maxd);
    for(int i = 1;i <= n;i ++)
        if(max(dist1[i],dist2[i]) == maxd)
            printf("%d ",i);
    fclose(stdin); fclose(stdout);
    return 0;
}




粗心的特派員(careless.cpp/c/pas)

【問題描述】

在APIO&CTSC2016名單下發時,yzy和fqk驚奇的發現名單中沒
有他們的名字,於是急忙聯繫了山東省特派員豆包哥lpy。卻被很無
奈的告知報名表被當成垃圾郵件扔進了垃圾桶。
粗心的特派員總是會做一些讓人啼笑皆非的事情, 比如在給各位
選手回覆郵件的時候,經常會因爲眼花而發錯人。但雖然眼花,所以
誤發的範圍也不會太大。具體地說,本應該發給第i個人的郵件,可
能會不小心發給編號爲[i-2,i+2]的人, 比如按照NOIP名單的順序應
該是lct1999,Oxer,TA,fye,davidxu,而本應該發給TA的郵件卻可
能發給這五位神犇中的任意一位。 但是他會保證每人都會且僅會收到
一封郵件。現在他想知道,他要將n封郵件發給n位選手,會有多少
種不同的結果?由於答案可能很大,請對10^9+7取模後輸出。

【輸入】

一行,一個整數n。

【輸出】

一行,一個整數表示答案。

【樣例輸入】

4

【樣例輸出】

14

【數據範圍】

對於10%的數據,n<=10
對於40%的數據,n<=500000
對於100%的數據,n<=10^16


第i個數可以放到[i-2,i+2]的區間內,求長度爲n的數列的合法排列方案數。

出題人給的做法是狀壓DP,dp[i][S]表示當前填到第i個數,[i-2,i+2]填/不填的狀態是S的方案數。要時刻保持[1,i-3]全爲1,這樣就能轉移了。

發現每次轉移都是一樣的,可以矩陣優化。32*32的矩陣。

玄學做法:暴力跑出前十項,然後強行設係數然後高斯消元…最後發現是f[n]=2f[n-1]+2[n-3]-f[n-5]……事實證明這是對的,然後矩陣就行了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

typedef long long LL; 
const int SZ = 500010;
const int INF = 1000000010;
const int mod = 1000000007;

struct matrix{
    int n,m;
    int num[20][20];
    matrix(int a = 0,int b = 0) :n(a),m(b) { memset(num,0,sizeof(num)); }
};

matrix operator *(const matrix &a,const matrix &b)
{
    matrix ans(a.n,b.m);
    for(int i = 1;i <= ans.n;i ++)
        for(int j = 1;j <= ans.m;j ++)
            for(int k = 1;k <= a.m;k ++)
                ans.num[i][j] = (ans.num[i][j] + (LL)a.num[i][k] * b.num[k][j] % mod) % mod;
    return ans;
}

matrix ans,f;

matrix ksm(matrix a,LL b)
{
    while(b)
    {
        if(b & 1) ans = ans * a;
        a = a * a;
        b >>= 1;
    }
    return ans;
}

void init()
{
    ans.n = 1; ans.m = 5;
    f.n = 5; f.m = 5;
    ans.num[1][1] = 31; ans.num[1][2] = 14; ans.num[1][3] = 6;  ans.num[1][4] = 2;  ans.num[1][5] = 1;

    f.num[1][1] = 2;    f.num[3][1] = 2;    f.num[5][1] = -1;
    f.num[1][2] = 1;    f.num[2][3] = 1;    f.num[3][4] = 1;    f.num[4][5] = 1;
}

int main()
{
    freopen("careless.in","r",stdin);
    freopen("careless.out","w",stdout);
    LL n;
    scanf("%lld",&n);
    init();
    if(n <= 5) printf("%d\n",ans.num[1][5 - n + 1]);
    else
    {
        ksm(f,n - 5);
        printf("%d\n",(ans.num[1][1] + mod) % mod); 
    }
    return 0;
}


/*

1 1
2 2
3 6
4 14
5 31

f[n] = 2f[i - 1] + 2 * f[i - 3] - f[i - 5]

*/

Description

浩浩蕩蕩的cg大軍發現了一座礦產資源極其豐富的城市,他們打算在這座城市實施新的採礦戰略。這個城市可以看成一棵有n個節點的有根樹,我們把每個節點用1到n的整數編號。爲了方便起見,對於任何一個非根節點v,它任何一個祖先的編號都嚴格小於v。樹上的每個節點表示一個礦點,每條邊表示一條街道。作爲cg大軍的一個小隊長,你擁有m個部下。你有一張二維的動態信息表,用Ti,j表示第i行第j列的數據。當你被允許開採某個區域時,你可以將你的部下分配至各個礦點。在第i個礦點安排j個人可以獲得Ti,j單位的礦產。允許開採的區域是這樣描述的:給你一對礦點(u,v),保證v是u的祖先(這裏定義祖先包括u本身);u爲你控制的區域,可以在以u爲根的子樹上任意分配部下;u到v的簡單路徑(不包括u但包括v,若u=v則包括u)爲探險路徑,在該路徑上你可以選擇至多一個礦點安排部下。你這次開採的收益爲安排有部下的礦點的收益之和。

Input

輸入的第一行包含5個正整數n、m、A、B、Q。n爲礦點的個數,m爲部下的數量。A、B、Q是與動態信息表有關的數據。第二行包含n-1個正整數,第i個數爲Fi+1,表示節點i+1的父親。接下來需要你用下文的方法依次生成n組數據,每組數據共m個。其中第i組的m個數據爲信息表中第i行的m個數據。緊接着一行包含一個正整數C,表示事件的數量。最後給出C行,每行描述一個事件。每個事件會先給出一個0或1的整數。如果該數爲0,則後面有一個正整數p,表示動態信息表有更新,你需要生成一組m個數據,來替換信息表中第p行的m個數據。如果該數爲1,則後面有兩個正整數u、v,表示出現了一個你可以開採的區域,你需要回答這次開採的收益。同一行的各個數之間均用一個空格隔開,沒有多餘的空格和換行。數據的生成方法如下:每次生成一組m個從小到大排列的數據,替換動態信息表的一行。其中,從小到大第j個數替換信息表中第j列的數。調用以下代碼m次並排序得到一組數據。(注意可能會出現重複的數)函數GetInt A←((A xor B)+(B div X)+(B * X))and Y B←((A xor B)+(A div X)+(A * X))and Y 返回(A xor B)mod Q 其中A、B、Q均用32位有符號整數保存(C/C++的signed long int類型,pascal的longint類型),X=216(2的16次方),Y=231-1(2的31次方-1),xor爲位異或運算,div爲整除運算,and爲位且運算,mod爲取餘運算。由於只保留了低31位,易得我們不用考慮數據的溢出問題。(注意每次A和B都會被改變)

Output

對於每個開採事件(開頭爲1的事件),輸出一行一個整數,爲每次的收益。

Sample Input

10 5 1 2 10

1 1 3 3 4 4 6 6 9

4

1 6 3

1 9 1

0 1

1 1 1

Sample Output

11

9

12

【樣例說明】

最初的信息表如下

1   2   3   4   5

1   0   1   1   2   2

2   0   5   7   7   9

3   1   2   3   4   5

4   0   1   2   4   5

5   2   4   7   8   8

6   0   2   3   8   9

7   1   3   5   6   8

8   3   3   3   7   8

9   0   1   2   3   9

10  0   0   1   4   4

變化後的第1行爲

1   1   1   1   4   7

第一次開採可以在礦點6、8、9、10任意安排,可以在礦點3或4中選取一個安排開採。一種最優安排是在礦點6安排4人,在礦點8安排1人。第二次開採可以在礦點9安排,可以在礦點6、4、3、1中選擇一個安排。一種最優安排是在礦點9安排1人,在礦點6安排4人。

HINT

有50%的數據,對於滿足2≤i≤n的整數i,Fi=i-1。這些數據中有40%的數據(即所有數據的20%)滿足n≤500,m≤20,C≤500。除上述數據,另有40%的數據滿足n≤500,m≤20,C≤500。對於100%的數據1≤n≤20000,1≤m≤50,1≤C≤2000。對於滿足2≤i≤n的整數i,1≤Fi<i。1≤A,B≤231-1,1≤Q≤10000。

Source


題面太長,複製的bzoj的。

清橙:http://www.tsinsen.com/A1219

線段樹每個點維護當前區間選一個點放i個人的最大價值,再來個隨便放i個點的最大價值,然後合併的時候像揹包那樣合併就行了

毒瘤題。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

typedef long long LL; 
const int SZ = 100010;
const int INF = 1000000010;

int n,m,A,B,Q;

int get_int()
{
    const int X = 1 << 16;
    const int Y = (1ll << 31ll) - 1ll;
    A=((A^B)+(B/X)+(B*X))&Y;
    B=((A^B)+(A/X)+(A*X))&Y;
    return (A^B)%Q;
}

void scan(int &n)
{
    n = 0;
    char a = getchar();
    bool flag = 0;
    while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
    while(a <= '9' && a >= '0') { n = n * 10 + a - '0'; a = getchar(); }
    if(flag) n = -n;
}

int head[SZ],nxt[SZ],to[SZ];

void build(int f,int t)
{
    static int tot = 1;
    to[++ tot] = t;
    nxt[tot] = head[f];
    head[f] = tot;
}

int top[SZ],sz[SZ],son[SZ],fa[SZ],deep[SZ];

void dfs_1(int u,int f)
{
    fa[u] = f;
    deep[u] = deep[f] + 1;
    sz[u] = 1;
    for(int i = head[u];i;i = nxt[i])
    {
        int v = to[i];
        dfs_1(v,u);
        sz[u] += sz[v];
        if(!son[u] || sz[son[u]] < sz[v]) son[u] = v;
    }
}

int pre[SZ],suf[SZ],dfs_clock = 0,intre[SZ];

void dfs_2(int u,int topu)
{
    top[u] = topu;
    pre[u] = ++ dfs_clock;
    intre[dfs_clock] = u;
    if(son[u]) dfs_2(son[u],topu);
    for(int i = head[u];i;i = nxt[i])
    {
        int v = to[i];
        if(v == son[u]) continue;
        dfs_2(v,v);
    }
    suf[u] = ++ dfs_clock;
}

int val[SZ][60];

struct segment{
    int l,r;
    int mx1[60],mx2[60]; //子樹 鏈 
}tree[SZ << 2];

void update(int p)
{
    memset(tree[p].mx1,0,sizeof(tree[p].mx1));
    int lch = p << 1,rch = p << 1 | 1;
//  for(int i = 0;i <= m;i ++)
//      for(int j = 0;j <= m - i;j ++)
//          tree[p].mx1[i + j] = max(tree[p].mx1[i + j],tree[lch].mx1[i] + tree[rch].mx1[j]);
    for(int i = m;i >= 0;i --)
        for(int j = i;j >= 0;j --)
            tree[p].mx1[i] = max(tree[p].mx1[i],tree[lch].mx1[j] + tree[rch].mx1[i - j]);

    for(int i = 1;i <= m;i ++) tree[p].mx2[i] = max(tree[lch].mx2[i],tree[rch].mx2[i]);
}

void build(int p,int l,int r)
{
    tree[p].l = l; tree[p].r = r;
    if(l == r)
    {
        for(int i = 1;i <= m;i ++)
            tree[p].mx1[i] = tree[p].mx2[i] = val[intre[l]][i];
        return ;
    }
    int mid = (l + r) >> 1;
    build(p << 1,l,mid); build(p << 1 | 1,mid + 1,r);
    update(p);
}

void change(int p,int pos)
{
    if(tree[p].l == tree[p].r)
    {
        for(int i = 1;i <= m;i ++)
            tree[p].mx1[i] = tree[p].mx2[i] = val[intre[pos]][i];
        return ;
    }
    int mid = (tree[p].l + tree[p].r) >> 1;
    if(pos <= mid) change(p << 1,pos);
    else change(p << 1 | 1,pos);
    update(p);
}

int ans1[60],ans2[60];

void ask_ans1(int p,int l,int r)
{
    if(l <= tree[p].l && tree[p].r <= r)
    {
        for(int i = m;i >= 0;i --)
            for(int j = i;j >= 0;j --)
                ans1[i] = max(ans1[i],ans1[i - j] + tree[p].mx1[j]);
        return ;
    }
    int mid = (tree[p].l + tree[p].r) >> 1;
    if(l <= mid) ask_ans1(p << 1,l,r);
    if(mid < r) ask_ans1(p << 1 | 1,l,r);
}

void ask_ans2(int p,int l,int r)
{
    if(l <= tree[p].l && tree[p].r <= r)
    {
        for(int i = 1;i <= m;i ++)
            ans2[i] = max(ans2[i],tree[p].mx2[i]);
        return ;
    }
    int mid = (tree[p].l + tree[p].r) >> 1;
    if(l <= mid) ask_ans2(p << 1,l,r);
    if(mid < r) ask_ans2(p << 1 | 1,l,r);
}

void find_ans2(int x,int y)
{
    int fx = top[x],fy = top[y];
    while(fx != fy)
    {
        if(deep[fx] < deep[fy]) swap(fx,fy),swap(x,y);
        ask_ans2(1,pre[fx],pre[x]);
        x = fa[fx]; fx = top[x];
    }
    if(deep[x] > deep[y]) swap(x,y);
    ask_ans2(1,pre[x],pre[y]);
}

int main()
{
    freopen("energy.in","r",stdin);
    freopen("energy.out","w",stdout);       
    scan(n); scan(m); scan(A); scan(B); scan(Q);
    for(int i = 2;i <= n;i ++)
    {
        int x;
        scan(x);
        build(x,i);
    }
    for(int i = 1;i <= n;i ++)
    {
        for(int j = 1;j <= m;j ++)
            val[i][j] = get_int();
        sort(val[i] + 1,val[i] + 1 + m);
    }
    dfs_1(1,0); dfs_2(1,1);
    build(1,1,dfs_clock);
    int C;
    scan(C);
    while(C --)
    {
        int opt;
        scan(opt);
        if(opt == 0)
        {
            int p;
            scan(p);
            for(int j = 1;j <= m;j ++)
                val[p][j] = get_int();
            sort(val[p] + 1,val[p] + 1 + m);
            change(1,pre[p]);
        }
        else
        {
            memset(ans1,0,sizeof(ans1));
            memset(ans2,0,sizeof(ans2));
            int u,v;
            scan(u); scan(v);
            ask_ans1(1,pre[u],suf[u]);
            if(u != v) find_ans2(v,fa[u]);
            int ans = 0;
            for(int i = 0;i <= m;i ++)
                ans = max(ans,ans1[i] + ans2[m - i]);
            printf("%d\n",ans);
        }
    }
    fclose(stdin); fclose(stdout);  
    return 0;
}
發佈了307 篇原創文章 · 獲贊 27 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章