zoukankan      html  css  js  c++  java
  • NOIP模拟 17.8.15

     NOIP模拟17.8.15

    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 行,每行三个数字 ai, bi, ci,表示 ai 欠 bi 的钱数为 ci。
    注意,数据中关于某两个人 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 ≤ 104。
    对于 80% 的数据,1 ≤ n ≤ 104,1 ≤ m ≤ 104。
    对于 100% 的数据,1 ≤ n ≤ 106,1 ≤ m ≤ 106。
    对于所有的数据,保证 1 ≤ ai, bi ≤ n, 0 < ci ≤ 100。

    【题解】

    水题,无fuck说

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <cstdlib>
     5 #define jue(a) (((a) >= 0) ? (a) : (-a))
     6 
     7 const int INF = 0x3f3f3f3f;
     8 const int MAXN = 20000000 + 10;
     9 
    10 inline void read(int &x)
    11 {
    12     x = 0;char ch = getchar(), c = ch;
    13     while(ch < '0' || ch > '9') c = ch, ch = getchar();
    14     while(ch <= '9' && ch >= '0')x = x * 10 + ch - '0', ch = getchar();
    15     if(c == '-')x = -x;
    16 }
    17 
    18 int n,m;
    19 int qian[MAXN], ans;//qian[i]正数表示第i个人被欠多少钱,负数表示欠别人多少钱 
    20 
    21 int main()
    22 {
    23     scanf("%d %d", &n, &m);
    24     register int tmp1, tmp2, tmp3;
    25     for(register int i = 1;i <= m;++ i)
    26     {
    27         read(tmp1), read(tmp2), read(tmp3);
    28         qian[tmp1] -=tmp3;
    29         qian[tmp2] += tmp3; 
    30     }
    31     for(register int i = 1;i <= n;++ i) ans += jue(qian[i]);
    32     printf("%d", (ans >> 1));
    33     return 0;
    34 }
    T1

    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,P erm(x) 表示字典序第 x 小的
    排列。
    现在,小 G 想求一下他手头两个排列的和。两个排列 p 和 q 的和为 sum =
    P erm((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 ≤ 105。
    8、9、10 测试点,1 ≤ n ≤ 5000。

    【题解】

    康托展开:

    一个序列的排名(从0开始计数) = Rank[n]*(n-1)!+Rank[n-1]*(n-2)!+….
    其中Rank[n]表示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^2

    天宇哥哥的做法是  从n往前找,p从-1向上累计,遇到没有用过的p就标记为用过,同时rank[n]--

    因为num[i + 1..n]求出后能得知num[i]能选哪一些数   也就是num[1...i]有哪一些数  从小到大枚举到对应排名即可

     1 #include <cstdio>
     2 #include <cstdlib>
     3 #include <cstring>
     4 #include <cstdlib>
     5 
     6 inline void read(int &x)
     7 {
     8     x = 0;char ch = getchar(), c = ch;
     9     while(ch < '0' || ch > '9')c = ch, ch = getchar();
    10     while(ch <= '9' && ch >= '0')x = x * 10 + ch - '0', ch = getchar();
    11     if(c == '-')x = -x;
    12 }
    13 
    14 const int INF = 0x3f3f3f3f;
    15 const int MAXN = 6000 + 10;
    16 
    17 int num1[MAXN], num2[MAXN];
    18 int rank1[MAXN], rank2[MAXN];
    19 int n, b[MAXN];
    20 
    21 int main()
    22 {
    23     read(n);
    24     for(register int i = n;i >= 1;-- i) read(num1[i]);
    25     for(register int i = n;i >= 1;-- i) read(num2[i]);
    26     register int p;
    27     for(register int i = 1;i <= n;++ i)
    28     {
    29         p = 0;
    30         for(register int j = 1;j < i;++ j)if(num1[j] < num1[i]) ++ p;
    31         rank1[i] += p;
    32         p = 0;
    33         for(register int j = 1;j < i;++ j)if(num2[j] < num2[i]) ++ p;
    34         rank2[i] += p;
    35         rank1[i] += rank2[i];
    36         rank1[i + 1] += rank1[i]/i;
    37         rank1[i] %= i;
    38     }
    39     rank1[n] %= n;
    40     for(register int i = n;i >= 1;-- i)
    41     {
    42         p = -1;
    43         while(rank1[i] >= 0)
    44         {
    45             ++ p;
    46             if(!b[p])-- rank1[i];
    47         }
    48         b[p] = 1;
    49         printf("%d ", p);
    50     }
    51     return 0;
    52 }
    T2

    C 剪树枝
    文件名 输入文件 输出文件 时间限制 空间限制
    tree.pas/c/cpp tree.in tree.out 1s 128MB
    【题目描述】
    rzyz 有一棵苹果树。苹果树有 n 个节点(也就是苹果),n − 1 条边(也就是
    树枝)。调皮的小 G 爬到苹果树上。他发现这棵苹果树上的苹果有两种:一种是黑
    苹果,一种是红苹果。小 G 想要剪掉 k 条树枝,将整棵树分成 k + 1 个部分。他
    想要保证每个部分里面有且仅有一个黑苹果。请问他一共有多少种剪树枝的方案?
    【输入格式】
    第一行一个数字 n,表示苹果树的节点(苹果)个数。
    第二行一共 n − 1 个数字 p0, p1, p2, p3, ..., pn−2,pi 表示第 i + 1 个节点和 pi 节
    点之间有一条边。注意,点的编号是 0 到 n − 1。
    第三行一共 n 个数字 x0, x1, x2, x3, ..., xn−1。如果 xi 是 1,表示 i 号节点是黑
    苹果;如果 xi 是 0,表示 i 号节点是红苹果。
    【输出格式】
    输出一个数字,表示总方案数。答案对 109 + 7 取模。
    【样例输入 1】
    3
    0 0
    0 1 1
    【样例输出 1】
    2
    【样例输入 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 ≤ 105。
    对于所有数据点,都有 0 ≤ pi ≤ n − 1,xi = 0 或 xi = 1。
    特别地,60% 中、80% 中、100% 中各有一个点,树的形态是一条链。

    【题解】

    做过的第二道树形DP,第一道A了的树形DP。我对树形DP理解还不够深入,所以下面的题解大家自行斟酌,

    抱着江信江疑的态度看

    正确性请尤其注意需要剪掉的时候,和每一个dp[i][1],能否保证性质:每个连通块有且仅有一个黑点

    dp[i][0]表示 i节点不能提供给父亲黑节点的方案数

    dp[i][1]表示i节点 能提供给父亲黑节点的而方案书

    初始:所有叶节点e,若为黑:dp[e][1] = 1,dp[e][0] = 1(可以切断与父节点连边);若为白:dp[e][1] = 0, dp[e][0] = 1

    转移:

    若i为黑色节点:

    dp[i][1] = πdp[son(i)][0]  这个地方保证了每个连通分块最多有一个黑节点

    dp[i][0] = dp[i][1] 即i为黑节点不给父亲黑节点的方案只能是减掉,相当于dp[i][1],保证每个连通分块至少有一个黑节点

    上述两条性质,使dp[i][1]的方案保证有且仅有一个黑节点

    若i为白色节点:

    dp[i][1] = Σ(dp[j][1] * πdp[son(i)][0])  son(i)不含j,j也是i的一个儿子  这个就是一个选黑,另一些选白    保证dp[i][1]的方案有且仅有一个黑节点

    dp[i][0] = πdp[son(i)][0] + dp[i][1]  即下面全是白,和能提供黑但减掉与父亲节点的边   

    答案即为dp[i][1]

    正确性我看了很久。。不知道对不对。。还请各位神犇指正

    转移有奇淫技巧,见代码及注释

      1 #include <iostream>
      2 #include <cstdio>
      3 #include <cstring>
      4 #include <cstdlib>
      5 #define max(a, b) ((a) > (b) ? (a) : (b))
      6 
      7 inline void read(int &x)
      8 {
      9     x = 0;char ch = getchar();char c = ch;
     10     while(ch < '0' || ch > '9')c = ch, ch = getchar();
     11     while(ch <= '9' && ch >= '0')x = x * 10 + ch - '0', ch = getchar();
     12     if(c == '-')x = -x;
     13 }
     14 
     15 const int INF = 0X3f3f3f3f;
     16 const int MAXN = 200000 + 10;
     17 const int MOD = 1000000000 + 7;
     18 
     19 struct Edge
     20 {
     21     int u,v,next;
     22     Edge(int _u, int _v, int _next){u = _u;v = _v;next = _next;}
     23     Edge(){}
     24 }edge[MAXN << 1];
     25 
     26 int head[MAXN], cnt;
     27 
     28 inline void insert(int a, int b)
     29 {
     30     edge[++cnt] = Edge(a,b,head[a]);
     31     head[a] = cnt;
     32 }
     33 
     34 
     35 int n,color[MAXN],dp[2][MAXN],b[MAXN],fa[MAXN];
     36 
     37 /*
     38 若i为黑:
     39 dp[i][0] = dp[i][1]
     40 dp[i][1] = πdp[son][0]
     41  42 dp[i][0] = πdp[son][0]
     43 dp[i][1] = dp[i][1]
     44 若i为白:
     45 dp[i][0] = πdp[son][0] + dp[i][1]
     46 dp[i][1] = Σdp[son1][1]* πdp[son(不含son1)][0]
     47 
     48 可以用dp[i][0]记录 πdp[son][0]  最后判颜色+dp[i][1]
     49 dp[i][1]则记录 Σdp[son1][1]* πdp[son(不含son1)][0]
     50 递推做到On
     51 我们用a,b,c,d表示儿子节点,如果只有a,b,c,式子为:
     52 a1 * b2 * c2 + a2 * b1 * c2 + a2 * b2 * c1
     53 加入d后:
     54 (a1 * b2 * c2 + a2 * b1 * c2 + a2 * b2 * c1) *d2 + a2 * b2 * c2 * d1  
     55 其中a2 * b2 * c2可直接用dp[i][0]递推过程值更新 
     56 
     57 */
     58 
     59 void dfs(int u)
     60 {
     61     b[u] = 1;
     62     register int size = 0, sum = 0, sum2 = 0, ok = 1;
     63     dp[0][u] = 1;
     64     for(register int pos = head[u];pos;pos = edge[pos].next)
     65     {
     66         int v = edge[pos].v;
     67         if(b[v])continue; 
     68         fa[v] = u;
     69         ++ size;
     70         ok = 0;
     71         dfs(v);
     72         dp[1][u] = ((long long)dp[1][u] * dp[0][v]) % MOD;
     73         dp[1][u] += ((long long)dp[0][u] * dp[1][v]) % MOD;
     74         if(dp[1][u] >= MOD)dp[1][u] -= MOD;
     75         dp[0][u] = ((long long)dp[0][u] * dp[0][v]) % MOD;
     76     }
     77     if(ok)
     78     {
     79         if(color[u]) dp[1][u] = 1, dp[0][u] = 1;
     80         else dp[1][u] = 0, dp[0][u] = 1;
     81         return;
     82     }
     83     if(color[u]) dp[1][u] = dp[0][u];
     84     else 
     85     {
     86         dp[0][u] += dp[1][u];
     87         if(dp[0][u] >= MOD)dp[0][u] -= MOD;
     88     }
     89     return;
     90 }
     91 
     92 int main()
     93 {
     94     read(n);
     95     register int tmp1;
     96     for(register int i = 0;i < n - 1;++ i) read(tmp1), insert(i + 2, tmp1 + 1), insert(tmp1 + 1, i + 2);
     97     for(register int i = 0;i < n;++ i) read(color[i + 1]); 
     98     dfs(1);
     99     printf("%d", dp[1][1]);
    100     return 0;
    101 }
    T3
  • 相关阅读:
    java代码---------常用的方法indexOf()和substring()方法的小结、主要是下标都是从0开始,很重要,错了就那个差远了啊
    java代码-----indexOf()方法--从字符串的某个字符的第一次出现的位子开始
    java代码----substring()方法是按索引截取字符串。。。下标0开始
    java代码啊==indexOf()方法返回字符第一次出现的位置
    java代码------实现从控制台输入整型,
    java代码----I/O流从控制台输入信息判断并抛出异常
    (转)pip和easy_install使用方式
    小小粉丝度度熊
    P1613 跑路
    P3819 松江1843路
  • 原文地址:https://www.cnblogs.com/huibixiaoxing/p/7384498.html
Copyright © 2011-2022 走看看