9.24NOIP模拟赛
来源:互联网 发布:如何防止成为备胎 知乎 编辑:程序博客网 时间:2024/06/01 10:36
简(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
题解:从小到大排序后取奇数项即可,不会做的可以考虑退竞晒(赛)了。
#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;typedef long long ll;const int MAXN=2e5+2;int n,nn,a[MAXN];int main() { freopen("simple.in","r",stdin); freopen("simple.out","w",stdout); scanf("%d",&n); nn=n<<1; for (register int i=1;i<=nn;++i) scanf("%d",&a[i]); sort(a+1,a+nn+1); ll ans=0; for (register int i=1;i<nn;i+=2) ans+=a[i]; cout<<ans<<endl; return 0;}
单(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能表示的范围。
题解:
t=0略,算出1的子节点的可以递推计算。
考虑t=1的情况.如果树是一条链(测试点7),则有:
记suf(i)为a[i]+a[i+1]+….+a[n-1]+a[n],pre(i)为a[1]+a[2]+…+a[i-1]+a[i],则b[i]=pre(1)+pre(2)+…+pre(i-1)+suf(i+1)+suf(i+2)+…+suf(n-1)+suf(n)
我们知道suf(2)+suf(3)+…+suf(n)的值(即b[1]),知道pre(1) + suf(3) + suf(4) +…+suf(n)的值(即b[2]),知道pre(1)+pre(2)+suf(4)+…+suf(n)的值(即b[3]),注意到这些式子有很多项是一样的,考虑作差.可以得到下面的式子:
b[2]-b[1]=pre(1)-suf(2),b[3]-b[2]=pre(2)-suf(3)…..b[i+1]-b[i]=pre(i)-suf(i+1)
这些式子是有实际意义的,考虑从b[1]变到b[2]时答案的变化量,变化的就是1和2之间连边的贡献.
同时,记SUM=a[1]+a[2]+…+a[n-1]+a[n],显然pre(i)+suf(i+1)=SUM,因此pre(i)=SUM-suf(i+1),将上面式子中所有pre换成suf,我们就知道了
SUM-2*suf(2) ,SUM-2*suf(3)…SUM-2*suf(n)的取值。
注意我们将n个式子作差之后得到了n-1个等式,实际上是丢弃了一些信息,只保留了b[i]之间的相对大小而忽略了b[i]自身的数值大小.于是考虑b[1]=suf(2)+suf(3)+suf(4)+… +suf(n),我们发现suf(2)到suf(n)都恰好出现了一次,而之前得到了n-1个形如SUM-2*suf(i)的式子,将这n-1个式子相加,suf(2),suf(3)…suf(n)前面的系数都是2,SUM的系数为(n-1),那么把这个式子加上2*b[1],按照定义:b[1]=suf(2)+suf(3)+suf(4)+…+suf(n),这是最巧妙的地方,相当于dis越大,就叠加越多次数!就得到了(n-1)*SUM,将求得的SUM代入之前的n-1个式子可以得到suf(2),suf(3),suf(4)……suf(n),差分一下即可得到a数组.
如果是一棵树,则将suf设做子树的a之和,做法和上面同理,此处不再赘述。
P.S.答案不超过int不代表过程不超过int!!!
#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;typedef long long ll;const int MAXN=1e5+4;ll sum[MAXN],dep[MAXN],siz[MAXN],S=0;int n,type,head[MAXN],edge;struct EDGE { int v,nxt;}e[MAXN<<1];ll a[MAXN],b[MAXN];ll sumc,temp;inline int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); return x*f;}inline void adde(int u,int v) { e[edge].nxt=head[u],e[edge].v=v,head[u]=edge++; e[edge].nxt=head[v],e[edge].v=u,head[v]=edge++;}inline void dfs0(int p,int fa,int d) { dep[p]=d,sum[p]=a[p]; for (int i=head[p];~i;i=e[i].nxt) { int v=e[i].v; if (v^fa) { dfs0(v,p,d+1); sum[p]+=sum[v]; } }}inline void cal0(int p,int fa) { for (int i=head[p];~i;i=e[i].nxt) { int v=e[i].v; if (v^fa) { b[v]=b[p]+S-(sum[v]<<1); cal0(v,p); } }}inline void cal1(int p,int fa) { siz[p]=1; for (int i=head[p];~i;i=e[i].nxt) { int v=e[i].v; if (v^fa) { sumc+=b[v]-b[p]; cal1(v,p); siz[p]+=siz[v]; } }}inline void cal_sum(int p,int fa) { for (int i=head[p];~i;i=e[i].nxt) { int v=e[i].v; if (v^fa) { sum[v]=(S-(b[v]-b[p]))>>1; cal_sum(v,p); } } a[p]=sum[p]; for (int i=head[p];~i;i=e[i].nxt) { int v=e[i].v; if (v^fa) a[p]-=sum[v]; }}int main() { freopen("single.in","r",stdin); freopen("single.out","w",stdout); int kase=read(); while (kase--) { edge=0; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); memset(head,-1,sizeof(head)); n=read(); for (register int i=1;i<n;++i) { int u=read(),v=read(); adde(u,v); } type=read(); S=0; if (!type) for (register int i=1;i<=n;++i) S+=a[i]=read(); else for (register int i=1;i<=n;++i) b[i]=read(); if (!type) { dfs0(1,0,0); b[1]=0;for (register int i=1;i<=n;++i) b[1]+=dep[i]*a[i]; cal0(1,0); for (register int i=1;i<=n;++i) printf("%I64d ",b[i]); puts(""); continue; } else { memset(a,0,sizeof(a)); memset(sum,0,sizeof(sum)); sumc=0; cal1(1,0);//calculate siz,delta sum[1]=S=(sumc+(b[1]<<1))/(n-1); cal_sum(1,0); for (register int p=1;p<=n;++p) printf("%I64d ",a[p]); puts(""); } } return 0;}
题(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】
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.
题解:
对于n<=100的40%数据:存在一个通用的DP,定义f[i][j][k]表示i步之后走到(j,k)的方案数,复杂度为O(n^3).
对于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)
P.S.对于typ=2的情况建议使用dp,dp[i][j]表示走了i步距离原点j个单位的方案数,然后O(n^2)进行dp,这样既好理解又省时间(不用计算Catalan数)
#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;typedef long long ll;const int MAXN=1e5+2;const ll MOD=1e9+7;int type;ll n,fac[MAXN],f[MAXN];ll dp[1002][1002];//i步,dis=jll fpow(ll a,ll b,ll p) { ll ret=1; while (b) { if (b&1) ret=ret*a%p; b>>=1,a=a*a%p; } return ret;}ll C(ll n,ll m) { return fac[n]*fpow(fac[n-m]*fac[m]%MOD,MOD-2,MOD)%MOD;}ll catalan(ll x) { return C(x<<1,x)*fpow(x+1,MOD-2,MOD)%MOD;}int main() { freopen("problem.in","r",stdin); freopen("problem.out","w",stdout); fac[0]=1; for (register int i=1;i<MAXN;++i) fac[i]=fac[i-1]*i%MOD; cin>>n>>type; if (!type) { ll ans=0; for (int i=0;i<=n;i+=2) ans=(ans+C(n,i)*C(i,i>>1)%MOD*C(n-i,(n-i)>>1)%MOD)%MOD; cout<<ans<<endl; } else if (type==1) cout<<catalan(n>>1)<<endl; else if (type==2) { memset(dp,0,sizeof(dp)); dp[0][0]=1; for (int i=0;i<n;++i) for (int j=0;j<=i;++j) { if (j==0) dp[i+1][j+1]=(dp[i+1][j+1]+dp[i][j]*4ll)%MOD; else dp[i+1][j+1]=(dp[i+1][j+1]+dp[i][j])%MOD; if (j) dp[i+1][j-1]=(dp[i+1][j-1]+dp[i][j])%MOD; } cout<<dp[n][0]<<endl; } else { ll ans=0; for (int i=0;i<=n;i+=2) ans=(ans+C(n,i)*catalan(i>>1)%MOD*catalan((n-i)>>1)%MOD)%MOD; cout<<ans<<endl; } return 0;}
- 9.24NOIP模拟赛
- 9.24NOIP模拟总结
- noip模拟赛 双城记
- 【noip模拟赛】密码
- 10.10NOIP模拟赛
- 10.08NOIP模拟赛
- 10.11NOIP模拟赛
- 10.12NOIP模拟赛
- 10.13NOIP模拟赛
- 【NOIP模拟赛】小奇挖矿
- NOIP模拟赛--军训
- 【NOIP模拟赛】数列
- noip模拟赛day8
- 16.1117 NOIP 模拟赛
- [NOIP模拟赛]单词
- [NOIP模拟赛]玻璃杯
- [NOIP模拟赛]游戏
- [NOIP模拟赛]统计
- Deep learning简介
- linux 操作apache
- linux kill
- JAVA大数据(1)--ZooKeeper的安装与部署
- Markdown 截图变图床贴图 Mac环境
- 9.24NOIP模拟赛
- LeetCode 0027
- my97 属性
- gcc builtin func
- windows下搭建python-flask-mysql
- Kotlin极简教程
- 结构体的地址与数据成员的地址
- erlang四大behaviour之二-gen_fsm
- React那些年, 踩过的一些坑(连载中...)