NOIP2017模拟赛(二)解题报告:
其实这是一份假的解题报告,因为我根本没有参加模拟赛。Ab_ever他们比赛的时候我还在家里睡午觉……(虽然我事后很快就把题目改到了满分)
T1:
星星光芒
题目描述
给你一个N个结点的有向图,而且给你一个N * N的邻接矩阵,表示两个结点之间是否有边。star是这样定义的 : 它有一个中心结点,并且中心结点至少有3个出度,出度用于计算star的光芒程度。 对于一个结点V来说,它可以有多颗star, 记为结点V的starnumber. 例如, 如果结点V 的出度是5, 那么结点V的 starnumber 通过计算等于16, 因为以V为中心结点而且光芒程度是3的有C(5,3) =10颗star, 光芒程度是4 的有C(5,4)=5颗star,光芒程度是5 的有C(5,5)=1颗star. 所以,结点V共有10 + 5 + 1 = 16颗不同的star。如果一个结点的出度是X,且X>=3,那么该结点共有:C(X,3) + C(X,4) + C(X,5) + ...+ C(X,X)颗不同的star。上面提到的C其实就是组合数。
下面我们定义有向图中的全明星路径:如果某条路径同时满足下面两个条件,则认为是全明星路径:
(1) 路径上的任意一个结点的star number大于0且不超过给定的整数G.
(2) 路径上的结点 Vi 和 Vi+1,要保证Vi+1 的star number不小于Vi 的star number。
你的任务是:计算给出的有向图,最长的全名星路径有多少个结点。如果全明星路径可以无限长,输出-1.如果没有全明星路径 (也就是给出的图中的所有结点的star number要么是0,要么大于G),则输出 0。
输入格式 1852.in
多组测试数据。
第一行:一个整数ng, 1 <= ng <= 5. 表示有ng组测试数据。
每组测试数据格式如下:
第一行:两个整数N、C. 2 <= N <=50, 1 <= G <= 10^9.
接下来有N行,每行有N列,表示邻接矩阵,如果有边则是1,否则是0。保证第i行的第i个是0.
输出格式 1852.out
最长的全名星路径有多少个结点。
ng行,每行对应一组输入数据。
输入样例 1852.in
2
5 1000
01110
10111
00000
00000
00000
4 1
0111
0000
0000
0000
输出样例 1852.out
2 (第一组测试数据:结点0的starnumber是1, 结点1的star number是5,其他结点的starnumber都是0)
1 (第二组测试数据)
题目分析:这就是一个很裸的floyd。我们预处理出每一个节点的star number,然后记f[i][j][k]为i到j,而且中间经过的节点编号不大于k的路径的最多节点数,用f[i][k][k-1]+f[k][j][k-1]-1更新它即可(前提是要能够到达这两个状态)。
我们与处理所有的组合数C,注意这个值很大(50取25就已经炸long long了),又不能模。但我们发现G只有10^9,当C大于10^9时直接令它等于10^9+1即可。Star number也是同理。
注意输出答案时要分类讨论,如果最后f[i][i][N]可以到达,就说明有环,否则看一下f[i][j][N]的最大值。如果所有状态f[i][j][N]都不可到达,我们还要考虑路径上只有一个点的情况,实在不行才输出0。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=55;
const int oo=1000000001;
int C[maxn][maxn];
int deg[maxn];
int g[maxn];
int f[maxn][maxn][maxn];
bool e[maxn][maxn];
int ng,N,G;
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
for (int i=0; i<maxn; i++) C[i][0]=1;
for (int i=1; i<maxn; i++)
for (int j=1; j<=i; j++)
{
C[i][j]=C[i-1][j]+C[i-1][j-1];
if (C[i][j]>oo) C[i][j]=oo;
}
scanf("%d",&ng);
while (ng--)
{
memset(deg,0,sizeof(deg));
scanf("%d%d",&N,&G);
for (int i=1; i<=N; i++)
for (int j=1; j<=N; j++)
{
char c=getchar();
while ( c!='0' && c!='1' ) c=getchar();
e[i][j]=(c-'0');
if (e[i][j]) deg[i]++;
}
memset(g,0,sizeof(g));
for (int i=1; i<=N; i++)
for (int j=3; j<=deg[i]; j++)
{
g[i]+=C[ deg[i] ][j];
if (g[i]>oo) g[i]=oo;
}
memset(f,-1,sizeof(f));
for (int i=1; i<=N; i++)
for (int j=1; j<=N; j++)
if ( e[i][j] && 0<g[i] && g[i]<=g[j] && g[j]<=G ) f[i][j][0]=2;
for (int k=1; k<=N; k++)
for (int i=1; i<=N; i++)
for (int j=1; j<=N; j++)
{
f[i][j][k]=f[i][j][k-1];
if (f[i][k][k-1]==-1) continue;
if (f[k][j][k-1]==-1) continue;
f[i][j][k]=max(f[i][j][k],f[i][k][k-1]+f[k][j][k-1]-1);
}
bool sol=true;
int ans=-1;
for (int i=1; i<=N; i++)
if (f[i][i][N]!=-1)
{
sol=false;
printf("-1\n");
break;
}
if (sol)
for (int i=1; i<=N; i++)
for (int j=1; j<=N; j++)
ans=max(ans,f[i][j][N]);
if (ans!=-1)
{
sol=false;
printf("%d\n",ans);
}
if (sol)
for (int i=1; i<=N; i++)
if ( 0<g[i] && g[i]<=G )
{
sol=false;
printf("1\n");
break;
}
if (sol) printf("0\n");
}
return 0;
}
T2:
职员分配
题目描述
由于Blue Mary呕心沥血的管理,Blue Mary的网络公司蒸蒸日上。现在一共拥有了n名职员,可惜没有任何的金钱和声誉。平均每名每天职员都可以给公司带来x单位金钱或者y单位声誉(名利不能双全)。并且可以花费z单位的金钱在人才交易市场发布广告招聘职员,每次发布广告三天以后就会招聘到一名职员,并且必须在发布广告并且招聘到职员的那一天才能发布下一次广告。
Blue Mary计划以最快的时间获得至少A单位金钱和至少B单位声誉,请你计算一下他至少需要多少时间才能达到他的目标。
输入格式 1853.in
输入有且仅有一行,包含六个整数n,x,y,z,A和B,意义如题目描述所述。
输出格式 1853.out
要求输出一行,包含一个整数,表示Blue Mary至少需要多少时间才能达到他的目标。
输入样例 1853.in
输入样例一:
1 2 3 4 5 6
输入样例二:
3 2 3 2 19 18
输出样例 1853.out
5
6
约定:
1<=n,x,y,z,A,B<=20
说明:
规定每天先去人才市场,然后再去赚钱(或名誉)。
题目分析:(这题数据真小,感觉爆搜写得好都可以……)我一开始想了一个n^4的DP。我们一开始用一个bool数组f[d][a][b][num]记录能否到达(到第d天,拥有a的金钱,获得b的声誉,有num个员工)的状态。然后我们发现num肯定是越大越好,于是改成f[d][a][b]=num。枚举n^3,转移n。
后来经ke_kxy大牛指点才恍然大悟:声誉其实是没有用的!你要获得b的声誉,其实相当于你要获得round(b/y)*x的金钱(round表示向上取整)。于是状态就变成了f[d][a]=num,表示到第d天,获得a元钱所拥有的最大员工(或者其他状态表示也可以),然后就可以随便乱搞了。
一开始写代码的时候当a<z我也去买员工,结果写炸了一次……至于a的那一维要开多大,10000总可以了吧?
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=26;
const int maxm=10000;
int f[maxn][maxm];
int n,x,y,z,a,b;
bool Judge(int x)
{
for (int i=a; i<maxm; i++)
if (f[x][i]!=-1) return true;
return false;
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
scanf("%d%d%d%d%d%d",&n,&x,&y,&z,&a,&b);
a+=((b+y-1)/y*x);
memset(f,-1,sizeof(f));
f[0][0]=n;
int t=0;
while ( !Judge(t) )
{
for (int i=0; i<a; i++)
if (f[t][i]!=-1)
{
int &h=f[t+1][ i+f[t][i]*x ];
h=max(h,f[t][i]);
if (i>=z)
{
int &k=f[t+3][ i+f[t][i]*x*3-z ];
k=max(k,f[t][i]+1);
}
}
t++;
}
printf("%d\n",t);
return 0;
}
T3:
加油站
题目描述
奶牛们开车到郊外旅游,由于驾驶技术不行,不小心撞到了石头,油箱漏油了。 现在汽车每走一单位距离,油箱就损耗和漏掉共一单位油。
为了修车,必须把车开到最近的小镇(不会超过10,000,000单位距离),到小镇的路可以认为是在一条直线上。从现在的位置到小镇途中, 共有 N (1 <= N<= 50,000) 个加油点,第i个加油点位于距离小镇有D_i (1 <= D_i < L)单位距离的地方,该加油站储油F_i (1<= F_i <= 100)单位.由于路途艰险,奶牛们想尽量把加油的次数减到最少.汽车的油箱可以认为是无穷大. 汽车现在为于距离小镇L个单位距离的地方,并且有P单位的油(1<= P <= 10,000,000).请你计算最少要加多少次油才能到达小镇,或者汽车根本开不到小镇. 注意:在同一地点,可能有多个加油站,但是,在计算时,它们是独立的,认为是两个不同的加油站。
输入格式 1854.in
* 第1行: 三个整数: N、 L、P.
* 第 2..N+1行: 两个整数: D_i 、 F_i. 对应着一个加油站。
输出格式 1854.out
* 一行: 到达小镇需要的最少的加油次数,如果无法到达小镇, 输出-1.
输入样例 1854.in
4 25 10
4 4
5 2
11 5
15 10
输出样例 1854.out
2
【输入解释】
汽车现在在距离小镇25个单位距离的地方,汽车有10个单位的汽油.
在途中, 有 4 个加油站,他们分别位于距离小镇: 4、 5、 11、15个单位距离的地方。(可以看出,它们跟汽车现在位置的距离分别是:21、20、14、10) 。 这4个加油站的储油量分别是: 4、 2、 5、10。
【输出解释】
汽车开10单位距离后, 停车加10单位的油, 再开4单位距离,停车加5单位的油, 然后就可以开到小镇了。
题目分析:极其傻逼的贪心……。我们发现只要你经过了某一个加油站,接下来无论什么时候加油都一样。于是我们看一下当前的油能不能到下一个加油站(或终点),不能的话在之前那些加油站之中,选一个油最多的加。
发现n=50000,各路大牛都在用数据结构维护最大值。然而只有我这种蒟蒻发现f<=100,开了个100的数组……
一开始我没有判断能不能到终点,只是判断能不能到最后一个加油站,导致拿了一次60分。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=50010;
const int maxf=105;
struct data
{
int d,f;
} sta[maxn];
int cnt[maxf];
int l,p;
int n,ans=0;
bool Comp(data x,data y)
{
return x.d>y.d;
}
int Get()
{
for (int i=100; i>=1; i--)
if (cnt[i])
{
cnt[i]--;
return i;
}
return 0;
}
int main()
{
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
scanf("%d%d%d",&n,&l,&p);
for (int i=1; i<=n; i++) scanf("%d%d",&sta[i].d,&sta[i].f);
sort(sta+1,sta+n+1,Comp);
n++;
sta[n].d=0;
sta[n].f=0;
int h=1;
while (sta[h].d>l) h++;
while (h<=n)
{
while (l-p>sta[h].d)
{
int x=Get();
if (!x)
{
printf("-1\n");
return 0;
}
p+=x;
ans++;
}
p-=(l-sta[h].d);
l=sta[h].d;
cnt[ sta[h].f ]++;
h++;
}
printf("%d\n",ans);
return 0;
}