1. 程式人生 > >8.22 NOIP 模擬題

8.22 NOIP 模擬題

next mil 黑蘋果 blog inux c++ cst mov cpp

8.22 NOIP 模擬題

技術分享
編譯命令 g++ -o * *.cpp
gcc -o * *.c
fpc *.pas
編譯器版本 g++/gcc 4.9.2
fpc 2.6.2
評測環境 64 位 Linux, 3.3GHZ CPU
評測軟件 Lemon
評測方式 忽略行末空格和回車
特別註意:c/c++ 選手使用 printf 輸出 64
位整數請使用%lld 1
註意事項


A 債務


文件名 輸入文件 輸出文件 時空限制
debt.pas/c/cpp debt.in debt.out 1s 128MB


題目描述


小 G 有一群好朋友,他們經常互相借錢。假如說有三個好朋友 A,B,C。A
欠 B 20 元,B 欠 C 20 元,總債務規模為 20+20=40 元。小 G 是個追求簡約的人,
他覺得這樣的債務太繁雜了。他認為,上面的債務可以完全等價為 A 欠 C 20 元,

B 既不欠別人,別人也不欠他。這樣總債務規模就壓縮到了 20 元。
現在給定 n 個人和 m 條債務關系。小 G 想找到一種新的債務方案,使得每個
人欠錢的總數不變,或被欠錢的總數不變(但是對象可以發生變化) ,並且使得總
債務規模最小。
輸入格式
輸入文件第一行兩個數字 n,m,含義如題目所述。
接下來 m 行,每行三個數字 a i ,b i ,c i ,表示 a i 欠 b i 的錢數為 c i 。
註意,數據中關於某兩個人 A 和 B 的債務信息可能出現多次,將其累加即可。
如”A 欠 B 20 元”、”A 欠 B 30 元”、”B 欠 A 10 元”,其等價為”A 欠 B 40 元”。


輸出格式


輸出文件共一行,輸出最小的總債務規模。


樣例輸入 1
5 3
1 2 10
2 3 1
2 4 1
2
樣例輸出 1
10
樣例輸入 2
4 3
1 2 1
2 3 1
3 1 1
樣例輸出 2
0
數據範圍
對於 30% 的數據,1 ≤ n ≤ 10,1 ≤ m ≤ 10。
對於 60% 的數據,1 ≤ n ≤ 100, 1 ≤ m ≤ 10 4 。
對於 80% 的數據,1 ≤ n ≤ 10 4 ,1 ≤ m ≤ 10 4 。
對於 100% 的數據,1 ≤ n ≤ 10 6 ,1 ≤ m ≤ 10 6 。
對於所有的數據,保證 1 ≤ a i ,b i ≤ n,0 < c i ≤ 100。

#include<iostream>
#include<cstdio>
#include<cstring>

#define N 1000007

using namespace std;
int val[N];
int x,y,z,n,m,ans;

inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c>9||c<0){if(c==-)f=-1;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    return x*f;
}

int main()
{
    freopen("debt.in","r",stdin);
    freopen("debt.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        x=read();y=read();z=read();
        val[y]+=z;val[x]-=z;
    }
    for(int i=1;i<=n;i++)
      if(val[i]<0) ans+=-val[i];
    printf("%d\n",ans);
    fclose(stdin);fclose(stdout);
    return 0;
}


B 排列



文件名 輸入文件 輸出文件 時空限制
perm.pas/c/cpp perm.in perm.out 1s 128MB


題目描述


小 G 喜歡玩排列。現在他手頭有兩個 n 的排列。n 的排列是由 0,1,2,...,n−1
這 n 的數字組成的。對於一個排列 p,Order(p) 表示 p 是字典序第 Order(p) 小的
排列(從 0 開始計數) 。對於小於 n! 的非負數 x,Perm(x) 表示字典序第 x 小的
排列。
現在,小 G 想求一下他手頭兩個排列的和。兩個排列 p 和 q 的和為 sum =
Perm((Order(p) + Order(q))%n!)。


輸入格式
輸入文件第一行一個數字 n,含義如題。
接下來兩行,每行 n 個用空格隔開的數字,表示小 G 手頭的兩個排列。
輸出格式
輸出一行 n 個數字,用空格隔開,表示兩個排列的和。


樣例輸入 1
2
0 1
1 0
樣例輸出 1
1 0
4
樣例輸入 2
3
1 2 0
2 1 0
樣例輸出 2
1 0 2
數據範圍
1、2、3、4 測試點,1 ≤ n ≤ 10。
5、6、7 測試點,1 ≤ n ≤ 5000,保證第二個排列的 Order ≤ 10 5 。
8、9、10 測試點,1 ≤ n ≤ 5000。

/*
可以看出Order  perm 互為反函數 
康托展開:
一個序列的排名(從0開始計數) = Rank[n]*(n-1)!+Rank[n-1]*(n-2)!+….
其中Rank[n]表示n位置上的數字在a[1]~a[n]中的排行,並且從0開始計數。
 
先求出兩個序列的康托展開式,相加
但是顯然階乘爆掉
於是我們只加rank數組
如果rank[i]這一位大於等於i,就按i進制進位
證明:
……rank[i] * (i - 1) ! + rank[i + 1] * i !……
若rank[i] > i 那麽可分解為
rank[i]%i * (i - 1)! + rank[i]/i * i! + rank[i + 1]*i!
所以進位顯然
mod n!的話,只需要忽略rank[i + 1]即可
可以從n往前找,p從-1向上累計,遇到沒有用過的p就標記為用過,同時rank[n]--
因為num[i + 1..n]求出後能得知num[i]能選哪一些數   也就是num[1...i]有哪一些數  從小到大枚舉到對應排名即可
*/
#include<iostream>
#include<cstdio>
#include<cstring>

#define N 6010
#define inf 0x3f3f3f3f

using namespace std;
int num1[N],num2[N],rank[N],rank2[N];
int n,b[N];

int main()
{
    freopen("perm.in","r",stdin);
    freopen("perm.out","w",stdout);
    scanf("%d",&n);
    for(int i=n;i>0;i--) scanf("%d",&num1[i]);
    for(int i=n;i>0;i--) scanf("%d",&num2[i]);
    int p;
    for(int i=1;i<=n;i++)
    {
        p=0;
        for(int j=1;j<i;j++) if(num1[j]<num1[i]) ++p;
        rank[i]+=p;
        p=0;
        for(int j=1;j<i;j++) if(num2[j]<num2[i]) ++p;
        rank2[i]+=p;
        rank[i]+=rank2[i];
        rank[i+1]+=rank[i]/i;
        rank[i]%=i;
    } rank[n]%=n;
    for(int i=n;i>=1;i--)
    {
        p=-1;
        while(rank[i]>=0)
        {
            ++p;
            if(!b[p]) --rank[i];
        }b[p]=1;
        printf("%d ",p);
    }return 0;
}


C 剪樹枝

文件名 輸入文件 輸出文件 時空限制
tree.pas/c/cpp tree.in tree.out 1s 128MB


題目描述


rzyz 有一棵蘋果樹。蘋果樹有 n 個節點(也就是蘋果) ,n − 1 條邊(也就是
樹枝) 。調皮的小 G 爬到蘋果樹上。他發現這棵蘋果樹上的蘋果有兩種:一種是黑
蘋果,一種是紅蘋果。小 G 想要剪掉 k 條樹枝,將整棵樹分成 k + 1 個部分。他
想要保證每個部分裏面有且僅有一個黑蘋果。請問他一共有多少種剪樹枝的方案?


輸入格式

第一行一個數字 n,表示蘋果樹的節點(蘋果)個數。
第二行一共 n − 1 個數字 p 0 ,p 1 ,p 2 ,p 3 ,...,p n−2 ,p i 表示第 i + 1 個節點和 p i 節
點之間有一條邊。註意,點的編號是 0 到 n − 1。
第三行一共 n 個數字 x 0 ,x 1 ,x 2 ,x 3 ,...,x n−1 。如果 x i 是 1,表示 i 號節點是黑
蘋果;如果 x i 是 0,表示 i 號節點是紅蘋果。
輸出格式
輸出一個數字,表示總方案數。 答案對 10 9 + 7 取模。


樣例輸入 1
3
0 0
0 1 1
樣例輸出 1
2
6
樣例輸入 2
6
0 1 1 0 4
1 1 0 0 1 0
樣例輸出 2
1
樣例輸入 3
10
0 1 2 1 4 4 4 0 8
0 0 0 1 0 1 1 0 0 1
樣例輸出 3
27


數據範圍

對於 30% 的數據,1 ≤ n ≤ 10。
對於 60% 的數據,1 ≤ n ≤ 100。
對於 80% 的數據,1 ≤ n ≤ 1000。
對於 100% 的數據,1 ≤ n ≤ 10 5 。
對於所有數據點,都有 0 ≤ p i ≤ n − 1,x i = 0 或 x i = 1。
特別地,60% 中、80% 中、100% 中各有一個點,樹的形態是一條鏈。

/*
一道比價雜亂的樹形dp,但貌似很經典。
f[i][0][0]代表第i個點是否砍掉,以它為根的子樹中有沒有黑點。
轉移的時候判斷當前點是黑是紅
黑點:f[u][1][0]=0;f[u][0][0]=0;(這兩種情況不成立,不管砍不砍以它為根的子樹中一定有黑點)
         f[u][1][1]=f[v][1][1]+f[v][0][0]的乘積(它被砍了子樹中只能有它一個黑點,
                  所以兒子中要麽沒有黑點[0][0]要麽有黑點被砍了[1][1])
         f[u][0][1]不知道暫時賦值為f[u][1][1] 
白點:f[u][1][0]這種情況對答案沒有貢獻設為0,因為它為白點子樹仍為白點都不能割;
         f[u][0][0]=f[v][1][1]+f[v][0][0]的乘積
                  (它的子樹中沒有黑點,兒子中要麽沒有黑點[0][0]要麽有黑點被砍了[1][1])
          f[u][1][1]=∑f[x][0][1]*(f[v][1][1]+f[v][0][0]),它為白以它為根的子樹中有且只有一個黑點,
                  則只有一個黑點兒子不被砍,其余兒子中要麽沒有黑點[0][0]要麽有黑點被砍了[1][1]
最後答案為f[0][0][1]
*/ 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>

#define mod 1000000007
#define N 100005

using namespace std;
long long f[N][2][2];
int tot,n;
int head[N],col[N],son[N];
struct node
{
    int to,next;
}e[N<<2];

inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c>9||c<0){if(c===)f=-1;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    return x*f;
}

inline void add(int u,int v)
{
    e[++tot].to=v;e[tot].next=head[u];head[u]=tot;
}

void dfs(int u,int v)
{
    for(int i=head[u];~i;i=e[i].next)
    {
        if(e[i].to==v) continue;
        dfs(e[i].to,u);
        son[u]=1;
    }
}

void dp(int u,int v)
{
    if(!son[u])
    {
        if(col[u])
        {
            f[u][1][1]=1;f[u][1][0]=0;
            f[u][0][1]=1;f[u][0][0]=0;
        }
        else
        {
            f[u][1][1]=0;f[u][1][0]=1;
            f[u][0][1]=0;f[u][0][0]=1;
        }return;    
    }
    for(int i=head[u];~i;i=e[i].next)
    {
        if(e[i].to==v) continue;
        dp(e[i].to,u);
    }
    if(col[u])
    {
        f[u][1][0]=0;f[u][0][0]=0;
        long long sum=1;
        for(int i=head[u];~i;i=e[i].next)
        {
            int t=e[i].to;
            if(t==v)continue;
            sum=sum*(f[t][0][0]+f[t][1][1])%mod;
        }
        f[u][0][1]+=sum;f[u][1][1]+=sum;
    }
    else
    {
        f[u][1][0]=0;
        long long sum=1;
        for(int i=head[u];~i;i=e[i].next)
        {
            int t=e[i].to;
            if(t==v)continue;
            sum=sum*(f[t][0][0]+f[t][1][1])%mod;
        }
        f[u][0][0]+=sum;
        long long ans=0;sum=1;
        for(int i=head[u];~i;i=e[i].next,sum=1)
        {
            if(e[i].to==v)continue;
            int tmp=f[e[i].to][0][1];
            if(!tmp)continue;
            for(int j=head[u];~j;j=e[j].next)
            {
                int t=e[j].to;
                if(t==v || j==i) continue;
                sum=sum*(f[t][0][0]+f[t][1][1])%mod;
            }
            ans=(ans+tmp*sum)%mod;
        }
        f[u][1][1]=ans%mod;f[u][0][1]=ans%mod;    
    }
}

int main()
{
//    freopen("tree.in","r",stdin);
//    freopen("tree.out","w",stdout);
/*  手動擴棧  
    int size = 256 << 16; // 256MB  
    char *p = (char*)malloc(size) + size;  
    __asm__("movl %0, %%esp\n" :: "r"(p));
*/    
    memset(head,-1,sizeof head);
    n=read();int x;
    for(int i=0;i<n-1;i++)
    {
        x=read();
        add(x,i+1);add(i+1,x);
    }
    for(int i=0;i<n;i++) col[i]=read();
    dfs(0,-1);dp(0,-1);
    cout<<f[0][0][1]%mod;
    return 0;
}

技術分享
100+40+0
T2沒想出來。
嗯沒錯,T3暴力掛了,水了30分鏈一分沒得,這讓大數據結構如此擅長水鏈分的我特別桑心了,,,,,,
唉,愁得我......T3還是沒耐心寫暴力吧(想到80分暴力貌似不對,寫了也不一定調出來),還是太弱了,myj大佬都開始挨個刷bzoj了.....不慫就是幹!晚上吧全排列寫5邊!!!(被stl坑了一把)。
T3正解一晚上沒懂,上午半上午才又找了一個看懂了,唉......
我就不信這個邪......
conclusion

8.22 NOIP 模擬題