829test
当教练说今天的题很难,而我却觉得很简单的时候——往往是我做错了
【准备】
Description
Alien的战舰不能搭载太多人,他需要在战舰上安装一些乘客客舱。可以安装的客舱有2p个型号,分别能搭载1至2p个乘客,每种型号有且仅有一个。为了方便搭载的过程,Alien希望恰好安装p个乘客客舱,且可搭载的乘客数量和必须为p的倍数。Alien想知道共有几种方案?
Input
本题有多组数据。第一行为数据组数n。
接下来n行每行包含一个奇质数p。
Output
对于每组数据输出一行,表示方案数。
Sample Input
2
3
5
Sample Output
8
52
数据范围
对于30%的数据 n⩽10,p⩽10
对于70%的数据 n⩽100,p⩽30
对于100%的数据 0⩽n⩽100,3⩽p⩽1000
题解
当年瞎DP乱做的可以水70,实在不想高精度了。如果非要AC的话,DP高精度肯定不好写亚。。。那还有别的办法吗??
匆匆赶去开会的老师留下一个神秘的公式就离开了:
\frac{C_{2p}^{p}-2}{p}+2
真是让人百思不得其解。于是OIER们决定自己证明(听了老师开完会后的详细讲解):
定义区间[1,2p],由于p是一个奇质数,可以证得:
\sum_1^pi|p , \sum_{p+1}^{2p}|p
即分别取区间[1,p]和区间[p+1,2p]内的p个数都符合要求,当前的方案数就为2了。剩下的就是跨区间的情况,比如在区间[1,p]选k个,在区间[p+1,2p]选p-k个。来举个栗子:
当p=7,k=3时:
假设当前选定的数为2,3,5,那么这三个数的和与7的余数是3。这里记作 f(2,3,5)=3 。如果让我们选的每个数都往后挪一个,可以得到如下式子:
f(2,3,5)=3
f(3,4,6)=6
f(4,5,7)=2
f(5,6,1)=5
f(6,7,2)=1
f(7,1,3)=4
f(1,2,4)=0
f(2,3,5)=3
观察一下,如果将原k个数中的每个数 + t ,(0<=t<=p-1),再与p取模,可以得到所有余数(0,1,2...,5,6)。如果记原来的和为sum,现在的和为sum+t*k(0<=t,k<=p-1),由于t与k中都没有质数p的因子,所以现在的和与原来的和模p的余数肯定是不一样的,换句话说,对于区间[1,p]内选k个数的方案,没有一种方案的数字之和模p的余数是相同的。
这里看懂了,后面就很简单了。如果我们在[p+1,2p]的选择是固定的,就要在[1,p]内找一个方案与之相匹配。记在[1,p]内选的k个数的和为sum1,记在[p+1,2p]内选的p-k个数的和sum2,则两种选择相匹配当且仅当 p| (sum1%p+sum2%p)。由于sum2%p是固定的,而刚刚证明过sum1%p在区间内是唯一的。所以对于其中一个区间内的选择如果固定,那么在另一个区间内的p种选择中只有唯一的一种可以与之相匹配。
于是我们就成功地证明了这个神奇的式子:
\frac{C_{2p}^{p}-2}{p}+2
最后别忘了写高精度压位哦(4位最佳)。
Code
#include<bits/stdc++.h>
#define maxn 2000
using namespace std;
int a[maxn+5],b[maxn+5],c[maxn+5];
int res[105][maxn+5],v[1005];
char tmp[maxn+5];
int n,p,cnt=0;
void bigOutput(int *a){
printf("%d",a[a[0]]);
for(int i=a[0]-1;i>0;i--){
printf("%04d",a[i]);
}
printf("\n");
}
int bigCmp(int *a,int *b){
if(a[0]>b[0])return 1;
if(a[0]<b[0])return -1;
for(int i=a[0];i>0;i--){
if(a[i]>b[i])return 1;
if(a[i]<b[i])return -1;
}
return 0;
}
void bigPlus(int *a,int *b,int *c){
int g=0,k=max(a[0],b[0]);
for(int i=1;i<=k;i++){
c[i]=a[i]+b[i]+g;
g=c[i]/10000;
c[i]%=10000;
}
if(g>0){
c[++k]=g;
}
c[0]=k;
}
void bigMinus(int *a,int *b,int *c){
int g=0,k=a[0];
for(int i=1;i<=k;i++){
c[i]=a[i]-b[i]-g;
if(c[i]<0){
c[i]+=10000;
g=1;
}
else g=0;
}
while(k>1 && c[k]==0)k--;
c[0]=k;
}
void bigMulInt(int *a,int b,int *c){
int k=a[0],g=0;
for(int i=1;i<=k;i++){
c[i]=a[i]*b+g;
g=c[i]/10000;
c[i]%=10000;
}
while(g>0){
k++;
c[k]=g%10000;
g/=10000;
}
c[0]=k;
}
void bigMulBig(int *a,int *b,int *c){
int k=a[0]+b[0];
for(int i=1;i<=a[0];i++){
for(int j=1;j<=b[0];j++){
c[i+j-1]+=a[i]*b[j];
c[i+j]+=c[i+j-1]/10000;
c[i+j-1]%=10000;
}
}
while(k>1 && c[k]==0)k--;
c[0]=k;
}
void bigDivBig(int *a,int b,int *c){
int k=a[0];
int g=0;
for(int i=a[0];i>0;i--){
g=g*10000+a[i];
c[i]=g/b;
g%=b;
}
while(k>1 && c[k]==0)k--;
c[0]=k;
}
int main(){
freopen("preparing.in","r",stdin);
freopen("preparing.out","w",stdout);
scanf("%d",&n);
while(n--){
scanf("%d",&p);
if(v[p]){
bigOutput(res[v[p]]);
continue;
}
cnt++;
v[p]=cnt;
memset(a,0,sizeof(a));
a[0]=1,a[1]=p+1;
for(int i=p+2;i<=2*p;i++){
memset(c,0,sizeof(c));
b[0]=1,b[1]=i;
bigMulBig(a,b,c);
memcpy(a,c,sizeof(a));
}
for(int i=1;i<=p;i++){
memset(c,0,sizeof(c));
bigDivBig(a,i,c);
memcpy(a,c,sizeof(a));
}
memset(c,0,sizeof(c));
b[0]=1,b[1]=2;
bigMinus(a,b,c);
memcpy(a,c,sizeof(a));
memset(c,0,sizeof(c));
bigDivBig(a,p,c);
memcpy(a,c,sizeof(a));
memset(c,0,sizeof(c));
b[0]=1,b[1]=2;
bigPlus(a,b,c);
bigOutput(c);
memcpy(res[cnt],c,sizeof(res[cnt]));
}
return 0;
}
【奖学金】
Description
为了选拔合适的学生入学,某大学发明了一种学术能力测试(简称SAT),这种测试的分数异常精确,每个学生的成绩可以用1到2,000,000,000之间的一个整数表示。
该大学的学费很贵,很多学生都负担不起,他们需要申请一些奖学金(1 ≤ 奖学金 ≤ 10000)。奖学金的预算都必须要从学校自身有限的基金中间扣除(设基金总额为F,0 ≤ F ≤ 2,000,000,000)。
很糟的是,该大学的只有N (1 ≤ N ≤ 19999)间教室,N是一个奇数,而一共有 C (N ≤ C ≤ 100,000) 名学生申请入学,为了让最多的学生接受教育,学校打算接受N个学生的申请,而且还想让这些学生的SAT 成绩的中位数尽可能地高。
所谓中位数,就是在一堆数字在排序后处在最中间的那个数字,比如{3,8,9,7,5}的中位数就是7。
给定每头学生的SAT 成绩和打算申请的奖学金数目,以及基金总数,确定接受哪些学生的申请才可以使成绩的中位数达到最大。
Input
第一行:三个用空格分开的整数:N,C和F。
第二行到C + 1行:每行有两个用空格分开的整数。第一个数是该学生的SAT 成绩,第二个数是其想申请的奖学金。
Output
第一行:一个整数,最大的中位数,如果现有基金不够资助N个学生,则输出-1。
Sample Input
3 5 70
30 25
50 21
20 20 5 18
35 30
Sample Output
35
数据范围
题目中已经给了丫。
题解
先将学生根据成绩降序排列,枚举中位数,只要满足基金条件就是我们想要的答案。
每次枚举到一个中位数s[i].val,要保证在其左边和右边分别选 (n-1)/2个学生,且这些学生申请的奖学金数目最少。可以先预处理出第i个学生的成绩为中位数时,ta左边应该选的学生的最小奖学金总和pre[i]和ta右边的最小奖学金总额nex[i]。这个操作用大根堆来实现。
Code
#include<bits/stdc++.h>
#define maxn 1000000
using namespace std;
int n,m;
long long F,mx=0,pre[maxn+5],nex[maxn+5];
priority_queue<long long> q;
struct students{
long long cost;
long long val;
}s[maxn+5];
bool cmp1(students a,students b){
return a.cost<b.cost;
}
bool cmp2(students a,students b){
return a.val==b.val?a.cost<b.cost:a.val>b.val;
}
int main(){
freopen("scholarship.in","r",stdin);
freopen("scholarship.out","w",stdout);
scanf("%d%d%lld",&n,&m,&F);
for(int i=1;i<=m;i++){
scanf("%lld%lld",&s[i].val,&s[i].cost);
}
sort(s+1,s+1+m,cmp1);
for(int i=1;i<=n;i++)mx+=s[i].cost;
if(mx>F){
printf("-1\n");
return 0;
}
sort(s+1,s+1+m,cmp2);
int k=(n-1)/2;
for(int i=1;i<=k;i++){
q.push(s[i].cost);
pre[k+1]+=s[i].cost;
}
for(int i=k+2;i<=m-k;i++){
long long x=q.top();
if(s[i-1].cost<x){
q.pop();
q.push(s[i-1].cost);
pre[i]=pre[i-1]-x+s[i-1].cost;
}
else pre[i]=pre[i-1];
}
for(int i=m;i>=m-k+1;i--){
q.push(s[i].cost);
nex[m-k]+=s[i].cost;
}
for(int i=m-k-1;i>=k+2;i--){
long long x=q.top();
if(s[i+1].cost<x){
q.pop();
q.push(s[i+1].cost);
nex[i]=nex[i+1]-x+s[i+1].cost;
}
else nex[i]=nex[i+1];
}
for(int i=k+1;i<=m-k;i++){
long long sum=s[i].cost;
sum+=pre[i];
if(sum>F)continue;
sum+=nex[i];
if(sum>F)continue;
printf("%lld\n",s[i].val);
break;
}
return 0;
}
【The Chocolate Spree】
Description
众所周知,Alice 和 Bob 是一对宿敌,每次博弈游戏中 Alice 和 Bob 总绞尽 脑汁企图战胜对手,而且很不公平地,每次都是 Alice 先手。然而他们都看上了大 M 种的一棵巧克力树,为了取得更多的巧克力,他们决 定联手合作。 大 M 的巧克力树由 n 个节点组成,每个节点 i 上都有 v[i]个巧克力,巧克 力树上有 n-1 条树枝连接这整棵树(保证任意两点间存在一条路径)。Alice 和 Bob 一开始都可以选择一个出发起点并获得该节点上的所有巧克 力,当然,Alice 先选。接下来,Alice 和 Bob 会依次移动到自己节点相邻的节 点上,并获得该节点上的所有巧克力。但是,任意节点都只能被经过一次,若 Alice 走过某个节点 x,则 Bob 不能到达这个节点,当然 Alice 也不能走回 x。请问他们总共最多能获得多少巧克力?
Input
第一行 1 个整数 n,表示节点个数;
第二行 n 个整数,表示 v[1..n];
接下来 n-1 行,每行两个整数 x 和 y,表示节点 x 与节点 y 之间有一条树枝相连。
Output
输出一行一个数,为总共能获得的最多的巧克力数。
Sample Input 1
9
1 2 3 4 5 6 7 8 9
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
Sample Output 1
25
Sample Input 2
2
10 20
1 2
Sample Output 2
30
数据范围
对于 20%的数据,n<=50;
对于 50%的数据,n<=5000;
对于 100%的数据,n<=100000,1<=v[i]<=10^9。
题解
一道关于树的双直径的神奇树上DP???竟然是道CF 633 F。
主要是,这道题的数组有那么一点点多(得不得了),需要仔细消化一下:
f(i,0)记录以i结点为根的子树中选两条不相交链的最大和。
f(i,1)记录以i结点为根的子树的直径。
son[i]记录以i结点的儿子为根的子树中最大的直径。
dis[i]记录从i开始能走到的最远的点,这条路径的权值。
p[i]记录dis[i]加上另外一条链的最大值。
看懂了吗???(没看懂也不管了自己画图意会一下)
Code
#include<bits/stdc++.h>
#define maxn 100000
using namespace std;
long long val[maxn+5],f[maxn+5][2],p[maxn+5],son[maxn+5],dis[maxn+5];
int n,head[maxn+5],newp=0;
struct node{
int u,v,nex;
}e[2*maxn+5];
void add(int a,int b){
newp++;
e[newp].u=a;
e[newp].v=b;
e[newp].nex=head[a];
head[a]=newp;
}
void dfs(int x,int fa){
f[x][0]=f[x][1]=dis[x]=p[x]=val[x];
son[x]=0;
for(int i=head[x];i;i=e[i].nex){
int y=e[i].v;
if(y==fa)continue;
dfs(y,x);
f[x][0]=max(f[x][0],f[y][0]);//记录x树的两条不相交链的最大和
f[x][0]=max(f[x][0],f[x][1]+f[y][1]);
f[x][0]=max(f[x][0],dis[x]+p[y]);
f[x][0]=max(f[x][0],p[x]+dis[y]);
f[x][1]=max(f[x][1],f[y][1]);//记录x树的直径
f[x][1]=max(f[x][1],dis[x]+dis[y]);
p[x]=max(p[x],val[x]+dis[y]+son[x]);//记录从x到叶子节点加上另外一条链的最大和
p[x]=max(p[x],val[x]+p[y]);
p[x]=max(p[x],dis[x]+f[y][1]);
son[x]=max(son[x],f[y][1]);//记录以x结点的儿子为根的子树中最大的直径
dis[x]=max(dis[x],val[x]+dis[y]);//记录x到最远的点的路径
}
}
int main(){
freopen("chocolate.in","r",stdin);
freopen("chocolate.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",&val[i]);
int x,y;
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,0);
printf("%lld\n",f[1][0]);
return 0;
}