• Codeforces Round #703 (Div. 2) (A~E)


    A. Shifting Stacks

    题目链接

    点我跳转

    题目大意

    给定 (N) 个土堆,第 (i) 个土堆有 (Ai) 个木块

    你可以将第 (i) 个土堆的木块转移至第 (i + 1) 个土堆

    问能否使土堆的木块数量构成上升序列

    解题思路

    贪心

    最优的构造方法即令土堆的木块数一次为 $0 , 1 , 2 , 3 ... $

    定义 (sum[i])(ai) 的前缀和,那么只要判断是否每个前缀和都满足 (sum[i] >= (i - 1) × i / 2)

    AC_Code

    #include<bits/stdc++.h>
    
    #define int long long
    
    using namespace std;
    
    const int N = 1e2 + 10;
    
    int n , a[N] , sum[N];
    
    signed main()
    {
    	int T = 1;
    	
    	cin >> T;
    	
    	while(T --)
    	{
    		cin >> n;
    		
    		for(int i = 1 ; i <= n ; i ++) cin >> a[i] , sum[i] = sum[i - 1] + a[i];
    	
    		bool ok = true;
    	
    		for(int i = 1 ; i <= n ; i ++)
    		{
    			if(sum[i] - (i - 1) * i / 2 < 0) {
    				ok = false ; break ;
    			}
    		}
    		
    		if(ok) cout << "YES
    ";
    		
    		else cout << "NO
    ";
    	}
    	
    	return 0;
    }
    

    B. Eastern Exhibition

    题目链接

    点我跳转

    题目大意

    二维平面上给定 (n) 个点,问存在多少个整数坐标点使得这些点到 (n) 个点的曼哈顿距离总和最小

    解题思路

    如果是一维直线,那么这些点只会存在于横坐标 (x) 的中位数之间,或者纵坐标 (y) 的中位数之间

    那么满足条件的点个数即为 横坐标 (x) 的中位数之间的长度 或 纵坐标 (y) 的中位数之间的长度

    拓展到二维这些点的个数即为横坐标 (x) 的中位数之间的长度 (×) 纵坐标 (y) 的中位数之间的长度

    (n) 为奇数时,(x) 的中位数只有 (1) 个,(y) 的中位数只有 (1) 个,所以答案为 (1)

    (n) 为偶数时,答案即是 (x) 的中位数之间的长度 (×) (y) 的中位数之间的长度

    AC_Code

    #include<bits/stdc++.h>
    
    #define int long long
    
    using namespace std;
    
    const int N = 1e3 + 10;
    
    int n , x[N] , y[N];
    
    signed main()
    {
    	int T = 1;
    	
    	cin >> T;
    	
    	while(T --)
    	{
    		cin >> n;
    		
    		for(int i = 1 ; i <= n ; i ++) cin >> x[i] >> y[i];
    		
    		sort(x + 1 , x + 1 + n) , sort(y + 1 , y + 1 + n);
    		
    		if(n & 1) cout << 1 << '
    ';
    		
    		else cout << (x[n / 2 + 1] - x[n / 2] + 1) * (y[n / 2 + 1] - y[n / 2] + 1) << '
    ';
    	}
    	
    	return 0;
    }
    

    C1&C2.Guessing the Greatest

    题目链接

    点我跳转

    题目大意

    交互问题

    有一个序列,每次你可以询问区间 ([L , R]) 的次大值的位置

    要求在 (20) 次询问内找出最大值的位置

    解题思路

    对于区间 ([L , R]) 的次大值位置为 (pos)

    那么最大值必然只出现在区间 ([L , pos - 1]) 或区间 ([pos + 1 , R])

    于是可以再次询问区间 ([L , pos]) 的次大值位置判断最大值是位于区间 ([L , pos-1]) 还是区间 ([pos +1 , R])

    (query([L , pos]) = pos) , 且 (pos != 1) 时,最大值处于区间 ([L , pos - 1])

    此时可以令 (R = pos - 1) , 并二分最大值的位置 (mid)

    如果 (query([mid , pos]) = pos) ,则可以确定最大值位于区间 ([mid , pos]) , 于是舍弃 ([l , mid - 1])

    否则可以确定最大值不位于区间 ([mid , pos]) , 于是二分的区间改为 ([l , mid - 1])

    否则最大值位于区间 ([pos +1 , R]) , 操作大致同上

    AC_Code

    #include<bits/stdc++.h> 
    
    using namespace std;
    
    int n , x;
    
    int query(int l , int r)
    {
    	cout << "? " << l << " " << r << '
    ';	
    	
    	cin >> x;
    	
    	return x;
    }
    signed main()
    {
    	
    	cin >> n;
    	
    	int l = 1 , r = n , res = 0;
    	
    	int pos = query(1 , n);
    	
    	if(pos != 1 && query(1 , pos) == pos)
    	{
    		l = 1 , r = pos - 1;
    		
    		while(l <= r)
    		{
    			int mid = l + r >> 1;
    			 
    			if(query(mid , pos) == pos) res = mid , l = mid + 1;
    			
    			else r = mid - 1;
    		}
    		
    	}
    	else
    	{
    		l = pos + 1 , r = n;
    			
    		while(l <= r)
    		{
    			int mid = l + r >> 1;
    			
    			if(query(pos , mid) == pos) res = mid , r = mid - 1;
    			
    			else l = mid + 1;
    		}
    	}
    	
    	cout << "! " << res << '
    ';
    	
    	return 0;
    }
    

    D. Max Median

    题目链接

    点我跳转

    题目大意

    给定一个序列,要求选出一个长度大于等于 (k) 的连续子序列

    使得该序列的中位数最大

    问最大中位数是多少?

    解题思路

    二分中位数 (x)

    将序列中大于等于它的数变为 (1) ,小于它的数变为 (0)

    假设选出的序列长度 (len) , 序列中 1 的个数为 (cnt)

    那么当 (cnt - 1 >= len / 2) 时,返回 (true)

    这里解释下两个问题 :

    1. 为什么式子左边是 (cnt - 1) 而不是 (cnt)?
    2. 为什么不是 (cnt - 1 = len / 2) 而是 (cnt - 1 >= len / 2)

    q1. (cnt - 1) 是因为序列中的某个 (1) 得作为 x 本身

    q2. 因为当 (cnt - 1 > len / 2) 时,必然存在一个大于 (x) 的数满足 (cnt - 1 = len / 2),所以需要返回 (true) 以向上改变二分区间

    我们可以枚举区间右端点,并选定左端点以使式子的结果为 (true)

    定义 (sum[i]) 为序列的前缀和,(L) 为当前区间的左端点 (-1) , (R) 为当前区间的右端点

    那么 (cnt = sum[R] - sum[L]) , (len = R - L)

    于是式子可以转变为 (sum[R] - sum[L] - 1 >= (R - L) / 2)

    通过移项 ,式子可变为 (2 × sum[R] - R - 2 >= 2 × sum[L] - L)

    因为 (R) 是我们枚举的 , 所以 (R、sum[R]) 都可认为常数

    为了让不等式成立,我们需要让不等号右边的式子尽可能小

    所以我们要取 (mi) (=) (min∑(2 * sum[i] - i) , i ∈[1 , i - k])

    因为每枚举一个右端点,只会出现一个新的可以选择的左端点 (i-k)

    所以 (mi) 对于每一个右端点只要 (O1) 就可以得到了

    注意:

    得到了 (mi) 我们不能直接改写式子为 (2 × sum[R] - R - 2 >= mi)

    因为 ((4 - 1) / 2 = 1) 而不是 (1.5)

    所以我们还要维护两个变量分别代表 (sum[L])(L)

    AC_Code

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N = 3e5 + 10;
    
    int n , m , k , a[N] , b[N] , sum[N];
    
    bool check(int x)
    {
    	int mi = 1e9 , pre = 1e9 , pos = -1e9;
    
    	for(int i = 1 ; i <= n ; i ++)
    	{
    		if(a[i] >= x) b[i] = 1;
    		
    		else b[i] = 0;
    		
    		sum[i] = sum[i - 1] + b[i];
    		
    		if(i - k >= 0 && mi >= 2 * sum[i - k] - (i - k))
    		{
    			mi = 2 * sum[i - k] - (i - k);
    			pos = i - k;
    			pre = sum[i - k];
    		}
    		
    		if(sum[i] - pre - 1 >= (i - pos) / 2) return true;
    	}
    	return false;
    }
    
    signed main()
    {	
    	cin >> n >> k ;
    	
    	for(int i = 1 ; i <= n ; i ++) cin >> a[i];
    	
    	int l = 1 , r = n , res = 0;
    	
    	while(l <= r)
    	{
    		int mid = l + r >> 1;
    		
    		if(check(mid)) l = mid + 1 , res = mid;
    		
    		else r = mid - 1;
    	}
    	
    	cout << res << '
    ';
    	
    	return 0;
    }
    

    E. Paired Payment

    题目链接

    点我跳转

    题目大意

    给定一张包含 (N) 个点,(M) 条边的无向图,每条边都有它的权值 (w) ((1 <= w <= 50))

    每次从一个节点出发,都必须走完两条边才能停下(中间经过的点不算到达过)

    途中的花费为两条边权值的和的平方

    问节点 (1) 出发到达每个点所需的最小花费分别为多少

    解题思路

    很多人用暴力的做法居然没有 (fst) ?这就很神奇 (hhh)

    update: 大数据貌似在 (fst) 之后才加上

    定义 (ans[i]) 表示从节点 (1) 出发到达节点 (i) 的最小花费

    定义 (dis[w][i]) 表示从某个点 (x) 走了 一条权值为 w 的边 到达节点 (i) ,而 (dis[w][i] = min(ans[x]))

    假设当前节点为 (u) , 它的相邻节点为 (v) , 它们之间的边权为 (w)

    那么不难得到

    dis[w][v] = min(dis[w][v] , ans[u]);
    
    for(int j = 1 ; j <= 50 ; j ++) ans[v] = min(ans[v] , dis[j][u] + (j + w) * (j + w));
    

    所以跑 (dijkstra) 时只要边权被更新我们就把该点入队

    但是传统的 (dijkstra) 在一个点入队后就会被打上标记从此不能再入队了

    而这里我们显然是需要一个点重复入队才能保证答案的最优

    如果选择重复入队就会使得复杂度爆炸?那怎么办呢?

    我们可以统计每个点入队的次数,当次数大于 (50) 时就不再入队

    这样可行是因为 (dis) 是由 (ans) 更新 , (ans) 是由 (ans) 和 边权 更新

    而边权最大只有 (50) , (50) 次入队足够让 (ans) 由所有的边权更新一遍了

    AC_Code

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int inf = 0x3f3f3f3f;
    
    const int N = 2e5 + 10;
    
    struct node{
        int dis , pos;
        bool operator <( const node &x )const{
            return x.dis < dis;
        }
    };
    
    struct Edge{
        int to , w , nex;
    }edge[N << 1];
    
    int head[N], dis[51][N] , tot , vis[N] , n , m , s , ans[N];
    
    inline void add_edge(int u , int v , int d)
    {
        edge[++ tot].w = d;
        
        edge[tot].to = v;
        
        edge[tot].nex = head[u];
        
        head[u] = tot;
    }
    inline void dijkstra(int s)
    {
        for(int i = 1 ; i <= n ; i ++) for(int j = 1 ; j <= 50 ; j ++) ans[i] = dis[j][i] = inf;
    	
        ans[s] = 0;
        
        priority_queue <node> que;
        
        que.push(node{0 , s});
        
        while(!que.empty())
        {
            node tmp = que.top();
            
            que.pop();
            
            int u = tmp.pos , d = tmp.dis ;
            
            if(vis[u] > 50) continue;
            
            vis[u] ++;
            
            for(int i = head[u] ; i ; i = edge[i].nex)
            {
                int v = edge[i].to , w = edge[i].w;
                      
                for(int j = 1 ; j <= 50 ; j ++)
                {
                	int cost = dis[j][u] + (j + w) * (j + w);
    				
    		if(cost < ans[v]) 
    		{
    		    ans[v] = cost;
    					
    		    if(vis[v] <= 50) que.push(node{ans[v] , v});
    	        }
    	    }
    			
    	        if(dis[w][v] > ans[u]) 
                    {
                	     dis[w][v] = ans[u];
                	
                	     if(vis[v] <= 50) que.push(node{dis[w][v] , v});
    	        }
             }
        }
    }
    signed main()
    {
        cin >> n >> m;
     
        for(int i = 1 ; i <= m ; i ++)
        {
            int u , v , w;
     
            cin >> u >> v >> w;
     
            add_edge(u , v , w);
     
            add_edge(v , u , w);
        }
     
          dijkstra(1);
     	
     	for(int i = 1 ; i <= n ; i ++)
    	{
    		if(ans[i] == inf) ans[i] = -1;
    		
    		cout << ans[i] << " ";
    	}
    	
        return 0;
    }
    
    凡所不能将我击倒的,都将使我更加强大
  • 相关阅读:
    C#中值类型和引用类型
    C#XML
    矩阵操作2
    scala安装
    Linux拷贝U盘文件(命令行)
    通过电脑,模拟点击手机屏幕 /手机自动点击,刷金币?
    python类
    矩阵操作
    数据预处理函数
    train_test_split数据切分
  • 原文地址:https://www.cnblogs.com/StarRoadTang/p/14418684.html
Copyright © 2020-2023  润新知