• 2018NOIP提高组Day1(铺设道路&货币系统&赛道修建)


    TIPS:数据包下载(NOI官网)

    P5019 铺设道路

    题目

    题目传送门

    思路

    一道水题
    对于每段区间[l,r]最小值为minn,所有di (i∈[l,r])-=minn
    ans += minn
    以每一个为0的d[i]作为分割点递归即可

    代码

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    int read(){
    	int re = 0 , sig = 1;
    	char c = getchar();
    	while(c < '0' || c > '9'){
    		if(c == '-')sig = -1;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9'){
    		re = (re << 1) + (re << 3) + c - '0';
    		c = getchar();
    	}
    	return re * sig;
    }
    int n;
    int d[100010];
    int ans;
    void dfs(int l , int r){
    //	cout << l << '	' << r << endl;
    	if(l > r)return;
    	int minn = (1 << 29) , k = -1;
    	for(int i = l ; i <= r ; i++){
    		if(minn > d[i])
    			minn = d[i] , k = i;
    	}
    	ans += minn;
    	for(int i = l ; i <= r ; i++)
    		d[i] -= minn;
    	int las = l;
    	for(int i = l ; i <= r ; i++){
    		if(d[i] == 0){
    			dfs(las , i - 1);
    			las = i + 1;
    		}
    	}
    	dfs(las , r);
    }
    int main(){
    	n = read();
    	for(int i = 1 ; i <= n ; i++)
    		d[i] = read();
    	dfs(1 , n);
    	cout << ans;
    	return 0;
    }
    

    P5020 货币系统

    题目

    题目传送门

    思路

    又一道水题
    对于每个a[i]若它可以表示为

    [a[i]=Sigma a[j]*k(j!=i 且k∈N) ]

    则a[i]在货币系统中可有可无,根据题意,不统计它即可,显然,所有的a[j]都是小于等于a[i]的。因此,我们可以将a从小到大数组排序,用类似无限背包的思想解决:设f[i]表示面值为i的货币是否可以被表示(false不能,true可以)初始化f[0]=true,当f[a[i]]=false时,a[i]不能被系统中的其他货币表示出来,统计a[i] (ans++)并用a[i]跑一边无限背包

    代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    int read(){
    	int re = 0 , sig = 1;
    	char c = getchar();
    	while(c < '0' || c > '9'){
    		if(c == '-')sig = -1;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9'){
    		re = (re << 1) + (re << 3) + c - '0';
    		c = getchar();
    	}
    	return re * sig;
    }
    int n , sum;
    int a[110];
    bool f[25010];
    int main(){
    	int T = read();
    	while(T--){
    		memset(f , 0 , sizeof(f));
    		f[0] = true;
    		sum = 0;
    		n = read();
    		for(int i = 1 ; i <= n ; i++)
    			a[i] = read();
    		sort(a + 1 , a + n + 1);
    		for(int i = 1 ; i <= n ; i++){
    			if(f[a[i]])continue;
    			sum++;
    			for(int j = 0 ; j <= a[n] - a[i] ; j ++)
    				if(f[j])
    					f[j + a[i]] = true;
    		}
    		printf("%d
    " , sum);
    	}
    	return 0;
    }
    

    P5021 赛道修建

    题目

    题目传送门
    有点

    55分思路:

    细看数据,发现我们可以获得55的高分(特殊数据点):
    在这里插入图片描述

    这些特殊数据的分加起来高达55 (然鹅我只拿了45)

    m == 1(树的直径)

    显然,就是求树的直径
    做法:(简单讲)

    1. 以任意一点为源点,求该点到树上所有点的距离O(n)
    2. 找到距离最大的点,设其为b点
    3. 以b点为源点,求b到树上所有点的距离O(n)
    4. 找到距离b最远的点,距离就是树的直径
    5. 证明:不会(但是好像也不用学会证明)

    bi=ai+1(链)

    就是一条链,原问题化为:
    在一个一维数列中,把数列划分成m段,使所有段中,和最小的段的和 最大
    赤裸裸的二分答案,枚举段的最小和,贪心跑一边即可
    贪心;

    	int cnt = 0 , sum = 0;
    	for(int i = 1 ; i < n ; i++){
    		if(edlen[i] + sum < maxn){//edlen为按顺序排列的道路长度,maxn为当前二分的最小段的最大值
    			sum += edlen[i];
    		}
    		else{
    			sum = 0;
    			cnt++;
    		}
    	}
    	return cnt >= m;//按照当前最小段最大值是否可以划分出m段
    

    ai=1(菊花图)

    就是这里少了10分
    就是菊花图,根据题意,每条赛道最多只能经过2条道路,排序计算即可

    
    	dfs(1);//将链式前向星的边权提取出来,存到edlen
    	sort(edlen + 1 , edlen + n , cmp);//大到小排序
    	int minn = (1 << 29);
    	for(int i = 1 ; i <= m ; i++){
    		int tmp = edlen[i] + (2 * m - i + 1 <= n - 1 ? edlen[2 * m - i + 1] : 0);
    		if(minn > tmp)
    			minn = tmp;
    	}
    	printf("%d" , minn);
    

    55分代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #include <algorithm>
    #define nn 50010
    using namespace std;
    int read(){
    	int re = 0 , sig = 1;
    	char c = getchar();
    	while(c < '0' || c > '9'){
    		if(c == '-')sig = -1;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9'){
    		re = (re << 1) + (re << 3) + c - '0';
    		c = getchar();
    	}
    	return re * sig;
    }
    
    struct node{
    	int to , len , nxt;
    }ed[nn * 2];
    int head[nn];
    void addedge(int u , int v , int len){
    	static int top = 1;
    	ed[top].len = len , ed[top].to = v;
    	ed[top].nxt = head[u] , head[u] = top;
    	top++;
    	ed[top].len = len , ed[top].to = u;
    	ed[top].nxt = head[v] , head[v] = top;
    	top++;
    }
    
    int n , m;
    int edlen[nn];
    bool vis[nn];
    void work1(){
    	int k , maxn;
    	int dis[nn];
    	queue <int> q;
    	memset(dis , -1 , sizeof(dis));
    	dis[1] = 0 , q.push(1);
    	while(!q.empty()){
    		k = q.front() , q.pop();
    		for(int i = head[k] ; i ; i = ed[i].nxt){
    			if(dis[ed[i].to] == -1){
    				dis[ed[i].to] = dis[k] + ed[i].len;
    				q.push(ed[i].to);
    			}
    		}
    	}
    	maxn = 0;
    	for(int i = 1 ; i <= n ; i++)
    		if(dis[i] > maxn)
    			maxn = dis[i] , k = i;
    	
    	
    	memset(dis , -1 , sizeof(dis));
    	dis[k] = 0 , q.push(k);
    	while(!q.empty()){
    		k = q.front() , q.pop();
    		for(int i = head[k] ; i ; i = ed[i].nxt){
    			if(dis[ed[i].to] == -1){
    				dis[ed[i].to] = dis[k] + ed[i].len;
    				q.push(ed[i].to);
    			}
    		}
    	}
    	maxn = 0;
    	for(int i = 1 ; i <= n ; i++)
    		if(dis[i] > maxn)
    			maxn = dis[i];
    	printf("%d" , maxn);
    }
    void dfs(int x){
    	static int siz = 1;
    	vis[x] = true;
    	for(int i = head[x] ; i ; i = ed[i].nxt){
    		if(!vis[ed[i].to]){
    			dfs(ed[i].to);
    			edlen[siz] = ed[i].len;
    			siz++;
    		}
    	}
    }
    bool cmp(int a , int b){return a > b;}
    void work2(){
    	dfs(1);
    	sort(edlen + 1 , edlen + n , cmp);
    	int minn = (1 << 29);
    	for(int i = 1 ; i <= m ; i++){
    		int tmp = edlen[i] + (2 * m - i + 1 <= n - 1 ? edlen[2 * m - i + 1] : 0);
    		if(minn > tmp)
    			minn = tmp;
    	}
    	printf("%d" , minn);
    }
    bool check(int maxn){
    	int cnt = 0 , sum = 0;
    	for(int i = 1 ; i < n ; i++){
    		if(edlen[i] + sum < maxn){
    			sum += edlen[i];
    		}
    		else{
    			sum = 0;
    			cnt++;
    		}
    	}
    	return cnt >= m;
    }
    void work3(){
    	dfs(1);
    	int sum = 0;
    	for(int i = 1 ; i < n ; i++)
    		sum += edlen[i];
    	int l = 1 , r = sum;
    	while(l < r){
    		int mid = (l + r) / 2;
    		if((l + r) & 1)
    			mid++;
    		if(check(mid))	l = mid;
    		else			r = mid - 1;
    	}
    	printf("%d" , l);
    }
    void work4(){
    	dfs(1);
    	int sum = 0;
    	for(int i = 1 ; i < n ; i++)
    		sum += edlen[i];
    	printf("%d" , sum);
    }
    int main(){
    	bool ty1 , ty2;
    	ty1 = ty2 = true;
    	n = read();	m = read();
    	for(int i = 1 ; i < n ; i++){
    		int u , v , len;
    		u = read() , v = read() , len = read();
    		addedge(u , v , len);
    		if(u != 1)		ty1 = false;
    		if(v != u + 1)	ty2 = false;
    	}
    	if(m == 1){
    		work1();
    		return 0;
    	}
    	if(m == n - 1){
    		work4();
    		return 0;
    	}
    	if(ty1){
    		work2();
    		return 0;
    	}
    	if(ty2){
    		work3();
    		return 0;
    	}
    	return 0;
    }
    

    100分思路

    建议不要看本蒟蒻的:搞了半天,WA变成了TLE,然后卡常,会用的技巧都用了,结果第6个和第18个还是TLE,无可奈何之下,第6个点求了树的直径,第18个点O2也过了话说O2真好用啊
    无O2O2

    讲正事儿
    首先是二分,同链的情况,设所有赛车路径中长度最小值为maxn
    对于每一个结点x,记以x为端点,x的子树中的某个结点为另一个端点,组成的所有的路径中长度最大且小于(不能等)maxn的值为f[x] (如果大于等于maxn的话直接cnt++就好了,不用记录,原因要看f[]的作用,待会就知道了)
    有了f,问题和菊花图有点像了。

    1. 首先,令si为x的某个子结点,则任意f[si]<maxn,即不能单独成为一条合法赛道(原因见f的定义)(我们称长度大于等于maxn的赛道合法);
    2. 但是,若将那些道路延长到x结点,就有可能成为合法赛道,并将这些赛道除去,我们将剩下的不合法赛道长度记录在tmp数组里面;
    3. 我们考虑以x为中转点,将剩下的赛道两两合并,使合并后合法赛道数量最大化
    4. 满足当前合法赛道数量最大的前提下,还要满足在x上方的点(父节点)的合法赛道数量最大化,因此,我们将没有合并的赛道中最长的一个记录在f[x]中,供x的父节点使用
    5. 由于使合法赛道数量最大的方案不止一种,我们又双叒叕考虑二分:将tmp取出,二分tmp的下标mid,若舍去tmp[mid]后合法赛道数量仍然可以到达最大值,就将mid前移(tmp[mid]增大),否则mid后移
    6. 关于如何求最大合法赛道数量的问题:贪心:(tmp已经从大到小排序的前提下)j=siz(siz为tmp的数组大小),i从前向后枚举,j向前找到第一个满足tmp[i]+tmp[j]>=maxn的树,cnt++,j--,当i>=j时结束
    7. 请仔细思考贪心的正确性

    100分代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #include <algorithm>
    #include <vector>
    #define rr register
    #define nn 50010
    using namespace std;
    int read(){
    	int re = 0 , sig = 1;
    	char c = getchar();
    	while(c < '0' || c > '9'){
    		if(c == '-')sig = -1;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9'){
    		re = (re << 1) + (re << 3) + c - '0';
    		c = getchar();
    	}
    	return re * sig;
    }
    
    struct node{
    	int to , len , nxt;
    }ed[nn * 2];
    int head[nn];
    void addedge(int u , int v , int len){
    	static int top = 1;
    	ed[top].len = len , ed[top].to = v;
    	ed[top].nxt = head[u] , head[u] = top;
    	top++;
    	ed[top].len = len , ed[top].to = u;
    	ed[top].nxt = head[v] , head[v] = top;
    	top++;
    }
    
    int n , m;
    int lensum = 0;
    bool vis[nn];
    int f[nn];
    int cnt;
    
    void work1(){//专门解决为第6个点TLE而copy55分的
    	int k , maxn;
    	int dis[nn];
    	queue <int> q;
    	memset(dis , -1 , sizeof(dis));
    	dis[1] = 0 , q.push(1);
    	while(!q.empty()){
    		k = q.front() , q.pop();
    		for(int i = head[k] ; i ; i = ed[i].nxt){
    			if(dis[ed[i].to] == -1){
    				dis[ed[i].to] = dis[k] + ed[i].len;
    				q.push(ed[i].to);
    			}
    		}
    	}
    	maxn = 0;
    	for(int i = 1 ; i <= n ; i++)
    		if(dis[i] > maxn)
    			maxn = dis[i] , k = i;
    	
    	
    	memset(dis , -1 , sizeof(dis));
    	dis[k] = 0 , q.push(k);
    	while(!q.empty()){
    		k = q.front() , q.pop();
    		for(int i = head[k] ; i ; i = ed[i].nxt){
    			if(dis[ed[i].to] == -1){
    				dis[ed[i].to] = dis[k] + ed[i].len;
    				q.push(ed[i].to);
    			}
    		}
    	}
    	maxn = 0;
    	for(int i = 1 ; i <= n ; i++)
    		if(dis[i] > maxn)
    			maxn = dis[i];
    	printf("%d" , maxn);
    }
    
    int tmp[nn] , siz = 0;//将tmp存为全局变量以节省空间
    bool cmp(int a , int b){return a > b;}
    void dfs(rr int x , rr int maxn){
    	vis[x] = true;
    	for(rr int i = head[x] ; i ; i = ed[i].nxt)//先遍历x的每一个子结点以防止tmp在递归时冲突
    		if(!vis[ed[i].to]){
    			dfs(ed[i].to , maxn);
    		}
    	
    	siz = 0;
    	for(rr int i = head[x] ; i ; i = ed[i].nxt)
    		if(!vis[ed[i].to]){
    			if(f[ed[i].to] + ed[i].len >= maxn) cnt++;//若此赛道延长到x点后长度到达maxn,直接cnt++
    			else tmp[++siz] = f[ed[i].to] + ed[i].len;//否则记录到tmp
    		}
    	sort(tmp + 1 , tmp + siz + 1 , cmp);//大到小排序
    	
    	int nowcnt = 0;
    	for(rr int i = 1 , j = siz ; i < j ; ++i){//求以x为根的树中最大合法赛道数量
    		while(tmp[i] + tmp[j] < maxn)
    			--j;
    		if(i >= j)
    			break;
    		++nowcnt;
    		--j;
    	}
    	f[x] = 0;
    //二分(不知道能不能像主函数里那个二分那样写,反正当时出错改了就没有再改回去)
    	rr int l = 1 , r = siz;
    	rr int mid;
    	rr int res = 0;
    	while(l <= r){
    		mid = (l + r) / 2;
    		
    		rr int cntt = 0;
    		rr int i = 1 , j = siz;
    		for( ; i < j ; i++){
    			if(i == mid)continue;//舍去mid结点求最大合法赛道数量
    			while(tmp[i] + tmp[j] < maxn || j == mid)
    				--j;
    			if(i >= j)
    				break;
    			++cntt;
    			--j;
    		}
    		if(nowcnt == cntt)	r = mid - 1 , res = mid;
    		else l = mid + 1;
    	}
    	cnt += nowcnt;
    	f[x] = tmp[res];
    	vis[x] = false;
    }
    bool check(int maxn){
    	memset(f , 0 , sizeof(f));//记得初始化
    	memset(vis , 0 , sizeof(vis));
    	cnt = 0;
    	dfs(1 , maxn);
    	return cnt >= m;
    }
    
    int main(){
    	bool ty1 , ty2;
    	ty1 = ty2 = true;
    	n = read();	m = read();
    	for(int i = 1 ; i < n ; i++){
    		int u , v , len;
    		u = read() , v = read() , len = read();
    		lensum += len;
    		addedge(u , v , len);
    	}
    	if(m == 1){
    		work1();
    		return 0;
    	}
    	rr int l = 1 , r = lensum;
    	r/=m;
    	while(l < r){//大二分
    		int mid = (l + r) / 2;
    		if((l + r) & 1) mid++;
    		if(check(mid))	l = mid;
    		else			r = mid - 1;
    	}
    	printf("%d" , l);
    	return 0;
    }
    
  • 相关阅读:
    20190127-将一个文件拆分为多个新文件
    20190125-找到列表第二大的数以及自己写一个冒泡排序
    20190121-n个人围成一圈,凡报到3的人退出圈子,最后留下的是原来第几号的那位
    20190120-自定义实现split方法
    20190118-自定义实现replace方法
    20190118-利用Python实现Pig Latin游戏
    20190116-将特定数字插入一个已经排序好的序列并且不改变其排序规则
    20190112-自定义实现字符串的操作方法,如strip,upper,title,ljust,center,zfill,find,rfind等
    20190110-用笨办法找到二维矩阵的鞍点
    我想转行—程序员转行自媒体
  • 原文地址:https://www.cnblogs.com/dream1024/p/13957582.html
Copyright © 2020-2023  润新知