9.24 NOIP模拟题(By liu_runda)

    话说衡水出的题怎么名不副其实啊… 一点都不水, 可能是因为达哥太强的原因, 毕竟河北rank1啊… 想当初东北集训的时候找他问题也很热心, 不过题就太不友善了…
        虽然这次的题确实比较水…

简(simple)
【题目描述】
大道至简.这就是出题人没有写题目背景的原因.
给出2n个数字,将它们划分成n组,每组的得分为这一组中两个数字的较小值.
求最大得分.
【输入格式】
第一行一个整数n表示正整数的数目.
接下来一行2n个空格隔开的整数a1,a2…a2n
【输出格式】
一行一个整数表示最大得分.
【样例输入】
2
1 3 1 2
【样例输出】
3
【数据范围】
对于10%的数据:n=2
对于另外20%的数据n<=7
对于另外20%的数据:n<=1000
对于另外20%的数据:ai<=100
对于100%的数据: n<=100000,1<=ai<=10^9

贪心sort一遍, 两两取最小值即可. 因为最小值无论如何对会被算到, 肯定让他找次小值一组比较赚. 以此类推.

#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long dnt;
const int maxn = 200005;
dnt ans;
int a[maxn], n;
int main(){
    freopen("simple.in", "r", stdin);
    freopen("simple.out", "w", stdout);
    scanf("%d", &n); n *= 2;
    for(register int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    sort(a + 1, a + n + 1);
    for(register int i = 1; i <= n; i += 2) ans += a[i];
    printf("%I64d", ans);
}

单(single)
【题目描述】
单车联通大街小巷.这就是出题人没有写题目背景的原因.
对于一棵树,认为每条边长度为1,每个点有一个权值a[i].dis(u,v)为点u到v的最短路径的边数.dis(u,u)=0.对每个点求出一个重要程度.点x的重要程度b[x]定义为其他点到这个点的距离乘上对应的点权再求和. 即:b[x]=a[1]*dis(1,x)+a[2]*dis(2,x)+….+a[n]*dis(n,x)
现在有很多树和对应的a数组,并求出了b数组.不幸的是,记录变得模糊不清了.幸运的是,树的形态完好地保存了下来,a数组和b数组至少有一个是完好无损的,但另一个数组完全看不清了.
希望你求出受损的数组.多组数据.
【输入格式】
第一行输入一个T,表示数据组数。接下来T组数据。
每组数据的第1行1个整数n表示树的点数.节点从1到n编号.
接下来n-1行每行两个整数u,v表示u和v之间有一条边.
接下来一行一个整数t,表示接下来数组的类型。
t=0则下一行是a数组,t=1则下一行是b数组。
接下来一行n个整数,表示保存完好的那个数组,第i个数表示a[i]或b[i]。
【输出格式】
T行,每组数据输出一行表示对应的a数组或b数组,数组的相邻元素用一个空格隔开。忽略行末空格和行尾回车.

【样例输入】
2
2 1 2
1 17 31
2
1 2
0
4
31 17
【样例输出】
31 17
17 31
【数据范围】
对于100%的数据,T=5, 2<=n<=100000,1<=u,v<=n,保证给出的n-1条边形成一棵树
对于100%的数据,t=0或t=1,1<=a[i]<=100,1<=b[i]<=10^9,t=1时保证给出的b数组对应唯一的一个a数组。
对于100%的数据,单个输入文件不会包含超过2000000个整数,这段话可以理解为,你不必考虑输入输出对程序运行时间的影响。
对于100%的数据,保证答案不会超过int能表示的范围
接下来的表格中描述了每个测试点的具体特征。每个测试点的5组数据均符合表格中对应的特征。
测试点编号
n
特殊限制
1
<=1000
均有t=0
2
<=5
均有t=1,答案中a[i]<=20
3
<=100
均有t=1
4
<=100
均有t=1
5
<=30000
所有边满足v=u+1
6
<=10^5
均有t=0
7
<=10^5
均有t=0
8
<=10^5
无特殊限制
9
<=10^5
无特殊限制
10
<=10^5
无特殊限制

求b很好求…求a的话可能回想到高斯消元, 但明显复杂度吃不消. 我的做法是假设了一个sum1. sum[u]代表u的子树a值之和. 设了sum[1]之后所有的a值都能推导出来. 很明显这个a值不正确.
因为 sum[v] = (sum[1] + b[u] - b[v]) / 2(v是u儿子), b值为定值, 同时a[u] = sum[u] - sigma sum[v](v 是 u的儿子). 发现如果已有的sum[1] 与真实的sum[1]差值为delta的话, 对于每个点的变化来说, 比如说某点u, a[u]会变化 ((1 - cnt[u]) * delta) / 2, 因为上面sum[v]的式子只有sum[1]在变化, b[u] , b[v] 都是给出来的. 那么因为对于每点来说delta都是相同的, 又因为本身有:
b[1] = a[2] * dis + a[3] * dis…. 那么我们算出用设出来的sum[1]推导出所有点的a, 用这个假a来推导出b[1], 那么与真实的给出来的b[1]的差值k, 这个差值k就等于:
delta((1 - cnt[2]) /2 * dis + (1 - cnt[3]) / 2 * dis ……)
这就是提出delta之后得到的式子, 因为括号里的都是已知的, k也知道, 那么delta就可以得到, 设的sum[1] + delta就是真实的sum[1], 就能推导出所有的真正的a.

#include<stdio.h>
#include<cstring>
#define Mercer   register int
#define clear(a) memset(a, 0, sizeof(a))
const int maxn = 100005;
typedef long long dnt;
dnt all, punchh, delta;
int num, n, t, T, fake, rightt;
int h[maxn], ans[maxn], a[maxn], b[maxn], sum[maxn], dis[maxn], cnt[maxn];
inline const int read(){
    register int x = 0;
    register char ch = getchar();
    while(ch < '0' || ch > '9') ch = getchar();
    while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x;
}
struct edge{ int nxt, v;}e[maxn * 2];
inline void add(int u, int v){
    e[++num].v = v, e[num].nxt = h[u], h[u] = num;
    e[++num].v = u, e[num].nxt = h[v], h[v] = num;
}
inline void ed(){
    num = 0, all = 0, punchh = 0, rightt = 0;
    clear(h), clear(ans), clear(cnt);
}
void dfs(int u, int fa){
    sum[u] = a[u]; 
    for(int i = h[u]; i; i = e[i].nxt){
        int v = e[i].v;
        if(v == fa) continue;
        dis[v] = dis[u] + 1, dfs(v, u); 
        sum[u] += sum[v];
    }
}
void calc_A(int u, int fa){
    for(int i = h[u]; i; i = e[i].nxt){
        int v = e[i].v;
        if(v == fa) continue;
        ans[v] = ans[u] + sum[1] - sum[v] * 2;
        calc_A(v, u);
    }
}
inline void work_A(){
    for(Mercer i = 1; i <= n; ++i) a[i] = read();
    dfs(1, 1);
    for(Mercer i = 1; i <= n; ++i) ans[1] += dis[i] * a[i]; 
    calc_A(1, 1);
}

void dfss(int u, int fa){
    a[u] = sum[u];
    for(int i = h[u]; i; i = e[i].nxt){
        int v = e[i].v;
        if(v == fa) continue;
        sum[v] = (sum[1] - b[v] + b[u]) / 2;
        dis[v] = dis[u] + 1, dfss(v, u);
        a[u] -= sum[v];
        cnt[u]++;
    }
}
inline void work_B(){
    for(Mercer i = 1; i <= n; ++i) b[i] = read();
    if((10000000 + b[1] - b[fake]) & 1) sum[1] = 10000001;
    else sum[1] = 10000000;
    dfss(1, 1);
    for(Mercer i = 2; i <= n; ++i) all += (dnt) a[i] * dis[i], punchh += dis[i];
    delta = (b[1] - all) * 2;
    for(Mercer i = 2; i <= n; ++i) rightt += (dnt) dis[i] * (1 - cnt[i]);
    delta /= rightt, sum[1] += delta;
    dfss(1, 1);
    for(Mercer i = 1; i <= n; ++i) ans[i] = a[i];
}
int main(){
    freopen("single.in", "r", stdin);
    freopen("single.out", "w", stdout);
    T = read();
    while(T--){
        n = read();
        for(Mercer i = 1; i < n; ++i){
            int u = read(), v = read();
            if(u != v) add(u, v);
            else continue;
            if(u == 1) fake = v;
            if(v == 1) fake = u;
        }
        t = read();
        if(!t) work_A();
        else   work_B();
        for(Mercer i = 1; i <= n; ++i) printf("%d ", ans[i]);
        puts("");
        ed();
    }
}

题(problem)
【题目描述】
出个题就好了.这就是出题人没有写题目背景的原因.
你在平面直角坐标系上.
你一开始位于(0,0).
每次可以在上/下/左/右四个方向中选一个走一步.
即:从(x,y)走到(x,y+1),(x,y-1),(x-1,y),(x+1,y)四个位置中的其中一个.
允许你走的步数已经确定为n.现在你想走n步之后回到(0,0).但这太简单了.你希望知道有多少种不同的方案能够使你在n步之后回到(0,0).当且仅当两种方案至少有一步走的方向不同,这两种方案被认为是不同的.
答案可能很大所以只需要输出答案对10^9+7取模后的结果.(10^9+7=1000000007,1和7之间有8个0)
这还是太简单了,所以你给能够到达的格点加上了一些限制.一共有三种限制,加上没有限制的情况,一共有四种情况,用0,1,2,3标号:
0.没有任何限制,可以到达坐标系上所有的点,即能到达的点集为{(x,y)|x,y为整数}
1.只允许到达x轴非负半轴上的点.即能到达的点集为{(x,y)|x为非负数,y=0}
2.只允许到达坐标轴上的点.即能到达的点集为{(x,y)|x=0或y=0}
3.只允许到达x轴非负半轴上的点,y轴非负半轴上的点以及第1象限的点.即能到达的点集为{(x,y)|x>=0,y>=0}
【输入格式】
一行两个整数(空格隔开)n和typ,分别表示你必须恰好走的步数和限制的种类.typ的含义见【题目描述】.
【输出格式】
一行一个整数ans,表示不同的方案数对10^9+7取模后的结果.
【样例输入0】
100 0
【样例输出0】
6
383726909
【样例输入1】
100 1
【样例输出1】
265470434
【样例输入2】
100 2
【样例输出2】
376611634
【样例输入3】
100 3
【样例输出3】
627595255
【数据范围】
10%的数据,typ=0,n<=100
10%的数据,typ=0,n<=1000
5%的数据, typ=0,n<=100000
10%的数据,typ=1,n<=100
10%的数据,typ=1,n<=1000
5%的数据, typ=1,n<=100000
10%的数据,typ=2,n<=100
15%的数据,typ=2,n<=1000
10%的数据,typ=3,n<=100
10%的数据,typ=3,n<=1000
5%的数据, typ=3,n<=100000
以上11部分数据没有交集.
100%的数据,保证n为偶数,2<=n<=100000,0<=typ<=3.

这道题每种type方法都不太一样, 主要考察动规, Catalan数. 这道题数轴上的因为要走回来所以Catalan很明显.
题解:
对于typ=1的数据:答案为catalan数,使用O(n)的catalan数递推公式或者利用组合数O(1)计算均可.catalan(n)=C(2n,n)/(n+1)
对于typ=0的数据:枚举横向移动了多少步.横向移动i步时(为了存在合法解,i必须是偶数),方案数为C(n,i)*C(i,i/2)*C((n-i),(n-i)/2)
对于typ=2的数据:f[i]表示走了i步回到原点的方案数,枚举第一次回到原点时走过的步数j(为了存在合法解,j为偶数),则此时方案数为f[i-j]*catalan(j/2-1),复杂度为O(n^2)所以最大范围只出到1000.
对于typ=3的数据:枚举横向移动了多少步.横向移动i步时(为了存在合法解,i必须是偶数),方案数为C(n,i)*catalan(i/2)*catalan((n-i)/2)

Tips: 解释一下为什么type2里那个catalan要有-1(加粗字体里的).因为要求第一次走回来, Catalan数如果想成括号匹配()()()的话, 你不能中间就走回来, 比如说()()()你来走回原点3次了, 所以要求是(……….), 一个括号必须在最外层, 所以要少一对, 即-1.

#include<stdio.h>
const int mod = 1e9 + 7;
const int maxn = 1e5 + 5;
typedef long long dnt;
int n, type;
dnt calc[maxn], inv[maxn], ans, f[1005];
inline dnt mpow(dnt a){
    dnt b = mod - 2, ed = 1;
    while(b){
        if(b & 1) ed = ed * a % mod;
        a = a * a % mod, b >>= 1;
    }
    return ed;
}
inline dnt C(dnt m, dnt mm){
    return calc[m] * inv[mm] % mod * inv[m - mm] % mod; 
}
inline dnt Catalan(int m){
    return C(2 * m, m) * mpow(m + 1) % mod; 
}
int main(){
    freopen("problem.in", "r", stdin);
    freopen("problem.out", "w", stdout);
    scanf("%d%d", &n, &type);
    calc[0] = 1;
    for(int i = 1; i <= n; ++i) calc[i] = calc[i - 1] * i % mod;
    inv[n] = mpow(calc[n]);
    for(int i = n; i; --i)  inv[i - 1]  = inv[i] * i % mod;
    if(!type){
        for(int i = 0; i <= n; i += 2)
            ans = (ans + C(n, i) * C(i, i/2) % mod * C(n - i, (n - i) / 2) % mod) % mod;
    }
    if(type == 1) ans = Catalan(n / 2);
    if(type == 2){
        f[0] = 1; f[2] = 4;
        for(int i = 4; i <= n; i += 2)
            for(int j = 2; j <= i; j += 2)
                f[i] = (f[i] + Catalan(j / 2 - 1) * 4 % mod * f[i - j] % mod) % mod;    
        ans = f[n];
    }
    if(type == 3){
        for(int i = 0; i <= n; i += 2)
            ans = (ans + C(n, i) * Catalan(i / 2) % mod * Catalan((n - i) / 2) % mod) % mod;    
    }   
    printf("%I64d\n", ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值