noip前8天,loli搞了一场模拟赛,四道题。也训练了一些考试的技能,也发现了一些问题。
%神%%王gay梁%% rank1,AK全场
T1没gang出来,用了topsort却发现不对。水了20分
T2是个比较水的dp,为数组就少开了一个数,拿了90分!!一定要细致细致再细致
T1:
1、比赛(contest)
N(1 <= N <= 100)个同学按1..N依次编号参加羽毛球比赛。每人的实力不尽相同,不存在两个人实力相同的情况。
比赛分成若干轮进行,每一轮是两个人对决。如果编号为A的同学的实力强于编号为B的同学(1 <= A <= N; 1 <= B <= N; A != B),那么她们的对决中,编号为A的同学总是能胜出。
你为了知道实力的具体排名,于是你找来M(1 <= M <= 4,500)轮比赛的结果,希望能根据这些信息,推断出尽可能多的同学得实力排名。比赛结果保证不会自相矛盾。
输入格式:
第1行:用空格隔开的2个整数:N 和 M
第2..M+1行: 每行2个整数A、B(用空格隔开的),描述一轮比赛,每行的第一个编号为胜者
输入样例:
5 5
4 3
4 2
3 2
1 2
2 5
输出格式:
1个整数,表示排名可确定的同学的数目
输出样例:
2
输出说明:
编号为2的同学的排名为第4,编号为5的同学排名最后。其他3人的排名仍无法确定。
后来知道这实际上就是传递闭包,用Floyed算法实现。
其实和决斗这道题比较像,都是有传递性的。如果之前的题做好这道题应该不难想出正解:
f[i][j]表示i在j的前面;g[i][j]表示i在j的后面
当某个人前面和后面的人都确定了,那么也就确定了他的名次
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,x,y,ru[1001];
int zhan[1001],tmp[1001],top,t;
struct Edge{
int to,next;
}edge[5001];
int ans;
bool f[1001][1001],g[1001][1001];
int main()
{
// freopen("contest.in","r",stdin);
// freopen("contest.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1; i<=m; i++)
{
scanf("%d%d",&x,&y);
f[x][y]=true; g[y][x]=true;
}
for (int k=1; k<=n; k++)
for (int i=1; i<=n; i++)
for (int j=1; j<=n; j++)
f[i][j]=f[i][j]||(f[i][k]&&f[k][j]);
for (int k=1; k<=n; k++)
for (int i=1; i<=n; i++)
for (int j=1; j<=n; j++)
g[i][j]=g[i][j]||(g[i][k]&&g[k][j]);
for (int i=1; i<=n; i++)
{
int qian=0; int hou=0;
for (int j=1; j<=n; j++)
{
if (f[i][j]) qian++;
if (g[i][j]) hou++;
}
if (qian+hou==n-1) ans++;
}
printf("%d",ans);
// fclose(stdin); fclose(stdout);
return 0;
}
2、运动计划(run)
地鼠误入一个隧道它必须在N(1 <= N <= 10,000)分钟内离开,否则隧道就会被人注满水,在每分钟的开始,地鼠会选择下一分钟是用来爬行还是休息。 地鼠的体力限制了她爬行的距离。更具体地,如果地鼠选择在第i分钟内爬行,她可以在这一分钟内爬可k_i(1 <= k_i <= 1,000)米,并且她的绝望指数会增加1。不过,无论何时地鼠的绝望指数都不能超过M(1 <= M <= 500)。如果地鼠选择休息,那么她的绝望指数就会每分钟减少1,但她必须休息到绝望指数恢复到0为止。在绝望指数为0时休息的话,绝望指数不会再变动。逃离开始时,地鼠的绝望指数为0。还有,在N分钟的逃离结束时,地鼠的绝望指数也必须恢复到0,否则她将对自己的一生失去信心。请你计算一下,地鼠最多能爬多少米。(已知地鼠爬行距离达到最大时恰好能逃离隧道。
输入格式:
第1行: 2个整数:N 和 M(用空格隔开)
第2。。N+1行: 第i+1行为1个整数:k_i
输入样例 (run.in):
5 2
5
3
4
2
10
输出格式:
输出一个整数,表示地鼠能爬行的最大距离
输出样例 (run.out):
9
输出说明:
第1分钟内爬行,在第2分钟内休息,在第3分钟内爬行,剩余时间用来休息。
很容易看出来是一道dp。
f[i][j]表示第i秒绝望值为j时的走的最大路径。
dp就是考虑这个状态是从上一个状态转移过来的,并且与上一个状态是怎么过来的无关。
dp这个东西,还是要多练啊。
到现在才发现自己刷的题那么少,在保证质量的前提下加大刷题量。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
int n,m,k[10001];
int f[10001][501];
int main()
{
scanf("%d%d",&n,&m);
for (int i=1; i<=n; i++) scanf("%d",&k[i]);
for (int i=1; i<=n; i++)
{
for (int j=1; j<i; j++)
f[i][0]=max(f[i][0],max(f[i-j][j],f[i-j][0]));
for (int j=1; j<=min(i,m); j++)
f[i][j]=max(f[i][j],f[i-1][j-1]+k[i]);
}
printf("%d",f[n][0]);
return 0;
}
3、找路径(road)
图上有N(1 <= N <= 1,000)个点,一共m(1 <= m<= 10,000)条边。 第i条边的端点为s_i、t_i,长度为L_i (1 <= L_i <= 1,000,000)。数据中保证每对{s_i,t_i}最多只出现1次。你的任务是找一条将点1和点N连起来的路径,如果存在不超过k(0 = < k <=n)条边的路径则输出0。否则输出第k+1大的边的最小值.
输入格式:
第1行: 3个整数:N m K(用空格隔开)
第2..m+1行: 第i+1行为3个整数:s_i t_i L_i(用空格隔开)
输入样例
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
输出格式:
1个整数
输出样例
4
输出说明:
路径为:1->3->2->5
解题报告
%%cansult
暴力(50)不能把road排序,应该扔到tmp里面排序。否则road就改变了
正解:”输出第k+1大的边的最小值”如果看成是最大值最小值问题,那不就是二分吗
二分一条边的长度,看起点到终点的路径中比这条边大的边是否超过了k条。如果大于继续找往小里找
最短路的变形:大于mid就是1,小于mid就是0
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN=100001;
const int MAXM=2000001;
int n,m,k,x,y,z;
struct Edge{
int to,next,dis;
}edge[MAXM];
int team[MAXN],dis[MAXN];
bool b[MAXN];
int ans=-1;
int num_edge,head[MAXN];
void add_edge(int from,int to,int dis)
{
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
edge[num_edge].dis=dis;
head[from]=num_edge;
}
bool spfa(int x)
{
memset(dis,0x7f,sizeof(dis));
int front=0,tail=1;
team[1]=1; dis[1]=0; b[1]=true;
while (front<=tail)
{
int now=team[++front];
b[now]=false;
for (int i=head[now]; i!=0; i=edge[i].next)
{
int s=0;
if (edge[i].dis>x) s=1;
if (dis[edge[i].to]>dis[now]+s)
{
dis[edge[i].to]=dis[now]+s;
if (!b[edge[i].to])
{
team[++tail]=edge[i].to;
b[edge[i].to]=true;
}
}
}
}
if (dis[n]<=k) return true;
else return false;
}
int main()
{
// freopen("road.in","r",stdin);
// freopen("road.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
int maxn=0;
for (int i=1; i<=m; i++)
{
scanf("%d%d%d",&x,&y,&z);
add_edge(x,y,z); add_edge(y,x,z); maxn=max(maxn,z);
}
int l=0; int r=2*maxn;
while (l<r)
{
int mid=(l+r)>>1;
if (spfa(mid)) {ans=mid; r=mid;}
else l=mid+1;
}
printf("%d",ans);
return 0;
}
要不50分代码也扔在这里?
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN=1001;
const int MAXM=20001;
int n,m,k,x,y,z;
struct Edge{
int to,next,dis;
}edge[MAXM];
bool b[MAXN];
int road[MAXN]; int tmp[MAXN];
int ans=0x7fffffff;
int num_edge,head[MAXN];
void add_edge(int from,int to,int dis)
{
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
edge[num_edge].dis=dis;
head[from]=num_edge;
}
bool comp(int a,int b) {return a>b;}
void work(int tot)
{
// for (int i=1; i<=tot; i++) printf("%d ",road[i]); printf("\n");
for (int i=1; i<=tot; i++) tmp[i]=road[i];
sort(tmp+1,tmp+1+tot,comp);
ans=min(ans,tmp[k+1]);
}
void dfs(int x,int step)
{
if (x==n)
{
if (step<=k) {printf("0"); exit(0);}
work(step); return;
}
for (int i=head[x]; i!=0; i=edge[i].next)
{
if (!b[edge[i].to])
{
b[edge[i].to]=true;
road[step+1]=edge[i].dis;
dfs(edge[i].to,step+1);
b[edge[i].to]=false;
}
}
}
int main()
{
// freopen("road.in","r",stdin);
// freopen("road.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for (int i=1; i<=m; i++)
{
scanf("%d%d%d",&x,&y,&z);
add_edge(x,y,z); add_edge(y,x,z);
}
// for (int i=1; i<=num_edge; i++) printf("%d %d\n",edge[i].to,edge[i].dis);
b[1]=true;
dfs(1,0);
if (ans==0x7fffffff)printf("-1");
else printf("%d",ans);
return 0;
}
4、道路(roads)
有一棵树,有m(1到150)个节点,如果砍掉几条边这棵树就会变成多棵树,求最少砍掉几条边可以在得到的树中至少有一颗节点个数为n(1到m)。
输入说明:第1行 m n , 第2到第m行,每行两个整数a 和b,表示a是b的父结点(1号点为根节点)。
输出说明:一个数为最少砍掉的边数。
输入样例:
11 6
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11
输出样例:
2
样例说明:砍掉1—4和1—5。
%%%wang gay liang
一道树形dp
/*设F[i][j] ,表示以i为根的子树中断边得到至少一个以j 为节点数的子树的最少断边数量。
是不是很长?先慢慢看懂。
然后状态转移方程为:
F[i][j]=min(F[p][k]+F[i][j-k]-2) {p为i的子节点}
初始化F[i][1]=与自己相连的边数
然后有一个细节要注意:
在子树的信息计算出来后:
倒着循环j ,不然之前计算的结果会影响后面的答案.*/
//f[i][j]表示在以i为根的子树里截取j个节点最少要删边数
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN=201;
const int MAXM=2001;
int n,m,x,y;
struct Edge{
int to,next;
}edge[MAXM];
int son[MAXN],f[201][201];
int ans=0x7fffffff;
int num_edge,head[MAXN];
void add_edge(int from,int to)
{
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
head[from]=num_edge;
}
void dfs(int x,int fa)
{
f[x][1]=son[x]-(fa!=0);
for (int i=head[x]; i!=0 ;i=edge[i].next)
{
if (edge[i].to!=fa)
{
dfs(edge[i].to,x);
for (int j=n; j>=1; j--)
for (int k=1; k<=j; k++)
f[x][j]=min(f[x][j],f[edge[i].to][k]+f[x][j-k]-1);
}
}
ans=min(ans,f[x][n]+(fa!=0));
}
int main()
{
freopen("roads.in","r",stdin);
freopen("roads.out","w",stdout);
scanf("%d%d",&m,&n);
memset(f,1,sizeof(f));
for (int i=1; i<=m-1; i++)
{
scanf("%d%d",&x,&y);
son[x]++; son[y]++;
add_edge(x,y); add_edge(y,x);
}
dfs(1,0);
printf("%d",ans);
return 0;
}