码谷国庆游记Day3模拟赛

考场:

第一道题看起来像最短路,emmm但他并不裸,我并不会写,于是开始我魔鬼的模拟??我也不知道叫啥,反正瞎搞一通过了样例,写了一个半小时,并无太大期望,准备拿5-10分的;

一个半小时其中空间写爆了,出现了一些鬼畜的提示,跳了许久,还卸载了一次Devc++【太难了】

第二道题,树的直径???真完美,一点不会写,暴力也不会??于是只剩下一个办法了,输出样例,本来准备大小样例分别输出,然鹅发现都是3的输出,完美??
第三道题,60%的n<=15,大概率2^n爆搜,再一看题目,这T3大概是唯一能做题目??写完爆搜大概还剩一个小时,再给我8个小时我也不会正解??放弃治疗

看到爆零崩了,觉得T3怎么着也是60分暴搜,不可能爆零叭

于是不服气下载Lemon开始自己测,三道题目,freopen的第二个输出最后stdout写成stdin。。。。爆零了,名副其实的爆零者本人了。。。

也海星,比全部没分更不让人伤心一些

用Lemon又测试了一下,15+10+60=85,这个不爆零的话大概Rank5??

也是佩服自己的,海星吧。。没noip出问题【强行自我安慰】

下面是题目部分

T1.bus

【问题描述】:
人类的发展总离不开一代又一代的探险者先驱。追寻前人的轨迹,今天的darling到了一个有趣的城市,和02居住的城市一样有趣。
在这个城市中有n个站台和m条公交线路,第i条公交线路由t[i]个站台组成,记为si_1,si_2…si_t[i].在0时刻,第i辆公交车会处在si_1站台,之后每个时刻公交都会到达路线中的下一个站台.
当公交到达终点站后,它下一个时刻将会回到出发的站台,注意一座站台可能在一条线路中出现多次,但不会相邻。
在0时刻,darling处在站台1。如果在某一时刻darling和公交在同一个站台,那么darling就可以上这辆公交车,并在任意时刻下车,上下车的过程并不会花费时间。作为一位环保的优秀旅行家,darling热爱乘坐公交出行。现在darling想要知道,如果他只乘坐公交出行,他分别能最早在何时到达每个站台,或是告诉他这是不可能的。
注意,darling一次只能乘坐一辆车,但在通往某个站台的过程中,darling可以乘坐许多辆不同的公交车。
【输入】:
输入的第一行是两个整数n,m;分别表示站台和线路的个数。
接下来的m行,每行的第一个数为t[i],表示线路的长度,随后的t[i]个整数描述一条公交线路。
【输出】:
共输出1行n-1个数,第i个数表示到达站台i+1的最小时间。如果不能到达,输出-1.
【样例输入】:
8 4
2 5 4
3 6 1 2
4 4 2 1 3
2 7 8
【样例输出】:
2 3 4 6 3 -1 -1

对于10%的数据,n<=3,m=1,t[i]<=4
对于10%的数据,n,m<=10,Σt[i]<=300
对于15%的数据,n,m<=50,Σt[i]<=300
对于15%的数据,n,m<=1000,Σt[i]<=20000
对于15%的数据,n,m<=5000,Σt[i]<=100000
对于100%的数据,n,m<=100000,Σt[i]<=200000

————分割————
15分考场代码:

#include<cstdio>
using namespace std;
int t[5001][10001],tn[5001],ans[100001],vis[100001];
//t[i][j]表示第i趟车第j时刻在哪个站台 ,ans[i]表示到达i站台所需时刻
//tn[i]第i趟车共几个站台 
int min_(int a,int b){return a<b?a:b;}
int main()
{
    //freopen("bus.in","r",stdin);
	//freopen("bus.out","w",stdout);
	int n,m,maxn=0,now=0,cnt=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=100001;i++)ans[i]=999999;
	for(int i=1;i<=m;i++)
	{
		now=0;
		scanf("%d",&tn[i]);
		//if(maxn<tn[i])maxn=tn[i]*2;
		for(int j=0;j<tn[i];j++){
			scanf("%d",&t[i][j]);//第i趟车第j时刻在那个站台 
			if(t[i][j]==1){
				ans[1]=min_(ans[1],j);
				now=j;
				vis[i]=1;
			}
		}
		if(now==0)continue;
		for(int j=now;j<tn[i];j++){
			ans[t[i][j]]=min_(ans[t[i][j]],j);
			//if(ans[t[i][j]]==j){tz[j]=t[i][j];tzz[j]=i;}
		}
		for(int j=0;j<now;j++){
			ans[t[i][j]]=min_(ans[t[i][j]],j+tn[i]);
			//if(ans[t[i][j]]==j+tn[i]){tz[j]=t[i][j];tzz[j]=i;}
		}
	}
	for(int i=1;i<=m;i++)
	{
		cnt=999999;now=0;
		if(vis[i]==1)continue;
		for(int j=0;j<tn[i];j++){
	    	if(ans[t[i][j]]<999999){
	    		if(ans[t[i][j]]<cnt)now=j,cnt=ans[t[i][j]];
			}
		}
		if(now==0)continue;
		for(int j=now;j<tn[i];j++){
			ans[t[i][j]]=min_(ans[t[i][j]],j+cnt);
			//if(ans[t[i][j]]==j){tz[j]=t[i][j];tzz[j]=i;} 
		}
		for(int j=0;j<now;j++){
			ans[t[i][j]]=min_(ans[t[i][j]],j+tn[i]+cnt);
			//if(ans[t[i][j]]==j+tn[i]){tz[j]=t[i][j];tzz[j]=i;}
		}
	}
	/*
	for(int z=1;z<=maxn*2;z++)
	{
		if(tz[z]==0)continue;
		for(int i=1;i<=m;i++){
	    	now=0;
			if(tzz[z]==i)continue;
		    for(int j=0;j<tn[i];j++)
			{
				if(t[i][j]==tz[z]){
					printf("%d",t[i][j]); 
					now=j;
			    for(int j=now;j<tn[i];j++){
	    	    	ans[t[i][j]]=min_(ans[t[i][j]],j);
	    	    	if(ans[t[i][j]]==j){tz[j]=t[i][j];tzz[j]=i;}
	        	}
	        	for(int j=0;j<now;j++){
	        		ans[t[i][j]]=min_(ans[t[i][j]],j+tn[i]);
	        		if(ans[t[i][j]]==j+tn[i]){tz[j]=t[i][j];tzz[j]=i;}
	           	}
				}
			} 
		}
	}*/
	for(int i=2;i<=n;i++){
		if(ans[i]==999999){
		    printf("-1 ");continue;
		}
    	printf("%d ",ans[i]);
	}
    //fclose(stdin);
    //fclose(stdout);
	return 0;
}

注释部分是最开始瞎搞的某种诡异方法,至于为啥考场给数组注释,因为我自己完全搞不明白自己的数组什么是什么

大概思路就是把有1站台的线路每一个到达时刻搞一搞,记录ans[i],到达i站台时刻,使用取min进行更新,然后再看没有1站台的线路,每一个站台是否可以到达,找时刻最小的一个,进入这段线路,更新线路上的站台答案,最后没被更新的就是到不了的。

至于正解思路:
【果然是最短路】

题目描述很吓人,但是慌张分析一下,我们发现到达每个点越早越好.所以就可以跑最短路辣.
如果有一辆车i,可以看做它是以t[i]为周期循环的,对于每一辆车从s[i][j]到s[i][j+1]连一条边,这条边当且仅当在时刻w可以经过,满足w%t[i]=j.所以就从s[i][j]到s[i][j+1]连一条边(t[i],j)即可.如果到达s[i][j]时刻比较早的话,可以在那一直等着

T2树

【问题描述】:
有一个n个点的树,每条边边权为1,你现在可以断开某一条边(x,y)然后再找两个点连一条新边使得其又恢复为一棵树并且使这棵新的树直径最大.
求新树直径最大是多少
【输入】:
第一行两个数 n
接下来n-1行每行两个数x,y表示x,y之间有一条边
【输出】:
输出一个整数表示新树直径最大是多少
【样例输入】:
4
1 2
2 3
2 4

【样例输出】:
3
【样例解释】:
样例输入输出解释
对于30%的数据,n<=3000
对于另外20%的数据,树的叶子节点有4个
对于100%的数据,n<=500000

————分割————
我??并不会写,输出了个3,代码就不放了【滑稽】

正解思路如下:

答案一定是割掉一条边后把剩下两棵树的直径拼起来,这两个直径不会重合.所以问题就变为了求一棵子树的直径加上除了这棵子树之外的直径的最大值.求一个子树内的直径可以直接维护子树的点到子树的根的最长链,次长链.求子树外的直径我们先找到子树u的根节点的父亲k,我们发现子树外的直径要么是k的除了u之外两个子树内的两条链拼起来的,要么是k的除了u之外的一个子树的链和k子树外的一条链拼起来,要么是k的除了u子树的子树内的直径,要么是k子树外的直径.
因此我们要维护子树内的点到根的最长链,次长链,第三长链,以及从子树外到子树根的最长链,同时维护子树外的直径和每个点的子树中的最长直径,次长直径.

T3戴亚蒙德

*【问题描述】 *
你有 n 个 “量⼦态” 的盒⼦,每个盒⼦⾥可能是⼀些钱也可能是⼀个钻⽯。
现在你知道如果打开第 i 个盒⼦,有 Pi/100 的概率能获得 Vi 的钱,有1-Pi/100 的概率能获得⼀个钻⽯。
现在你想知道,⾃⼰恰好获得 k(0≤k≤n)个钻⽯,并且获得钱数⼤于等于 m 的概率是多少。
请你对 0≤k≤n 输出 n+1 个答案。
答案四舍五⼊保留 3 位⼩数。
【输入格式】
第⼀⾏两个整数 n,m,见题意。
接下来 n ⾏,每⾏两个整数 Vi , Pi。
*【输出格式】 *
输出共 n+1 ⾏,表⽰ 0 ≤ k ≤ n 的答案。
*【样例输入】 *
2 3
2 50
3 50
【样例输出】
0.250
0.250
0.000
【数据规模和约定】
对于 30% 的数据, n ≤ 10。
对于 60% 的数据, n ≤ 15
对于 100% 的数据,n ≤ 30, 1 ≤ Pi ≤ 99, 1 ≤ Vi ≤ 10^7, 1 ≤ m ≤ 10^7

————分割————
60分暴力还是比较明显【滑稽】【我也只会暴力这种东西了(难)】
2^n复杂度的搜索
num为当前搜到的盒子i,cnt为获得钻石数量,money就是money,P为概率,zuan就是题目中k

#include <cstdio>
int n,m,v[101],p[101];
double ans;
void sou(int num,int cnt,int money,double P,int zuan)
{
    if(num==n+1){
        if(cnt==zuan&&money>=m) ans+=P;
        return ;
    }
    sou(num+1,cnt,money+v[num],(double)p[num]/100*P,zuan);//得到钱
    sou(num+1,cnt+1,money,(double)(1.0-(double)p[num]/100.0)*P,zuan);//得到钻石
}
int main()
{
    //freopen("diamond.in","r",stdin);
	//freopen("diamond.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
        scanf("%d%d",&v[i],&p[i]);
    for(int i=0;i<=n;i++)
    {
        ans=0.0;
        sou(1,0,0,1.0,i);
        printf("%.3lf\n",ans);
    }
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

至于正解

N<=15直接2^15暴力枚举,n<=30自然就想到了meet in the middle,先215枚举前15个盒子打开的是钱还是钻石,在215枚举后15个盒子.然后将这两段信息合并.
记vector a[i][j].w/p表示前15个盒子打开了i个钻石的所有情况(钱和对应的概率),记b[i][j]表示后15个盒子的所有情况,然后对于a,b数组按照钱排个序,对于每个a[i][j]找到一个b[k-i][l]满足b[k-i][l].w+a[i][j].w>=m,二分然后求b[k-i][l].p的后缀和即可.

hin有道理的样子

我太蒟蒻了。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值