2019-CCPC网络赛部分题解

1001 &

题意简述:
给定正整数a和b,找到一个最小的正整数c,使得(ac)&(bc)最小。
解题思路:
两个数按位与最小结果肯定是0啊,所以对于a和b,如果它们对应二进制位上有0,则c置为0即可,因为0^0 = 0 , 这就保证了异或之后肯定有一个是0,再&之后还是0;若它们对应二进制位都是1,则只能置c的对应二进制位为1了,因为1^1 = 0。

值得注意的是,所求结果c是正整数,所以如果当c = 0时,则需要从低位向高位,将0-1或1-0位时,c对应二进制位 = 1。
代码示例:

#include<cstdio>
#include<iostream>
typedef long long ll;
int t;
ll a,b;
void solve(){
    ll c = 0;
    for(int i = 0;i <= 32;i++){
        if((a&(1ll<<i)) && (b&(1ll<<i))) c = c|(1ll<<i);
    }
    if(c == 0){
        for(int i = 0;i <= 32;i++){
            if((a&(1ll<<i)) != (b&(1ll<<i))){
                c = c|(1ll<<i);
                break;
            }
        }
    }
    printf("%lld\n",c);
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%lld%lld",&a,&b);
        solve();
    }
    return 0;
}
1002 array

测试地址
题意简述:
给定1~n共n个数组成的序列,有m次操作,每次操作有两种可能:

  1. (1,pos)令 a p o s = a p o s + 1 , 000 , 000 a_{pos} = a_{pos}+1,000,000 apos=apos+1,000,000
  2. (2, r , k) 回答不在 a i ( 1 &lt; = i &lt; = r ) a_i(1&lt;= i &lt;= r) ai(1<=i<=r)内的,大于等于k的最小值。

强制在线。
解题思路:
首先是理解题意,然后如果按照题目要求来解题,我们需要每次都通过遍历来排除[1,r]内的元素,显然会超时。由于题目中 a i a_i ai是各不相同的,又是小于n的,因此我们可以将原问题转化为求“大于等于k的所有元素中,第一个下标大于r的元素的值”。对于这个问题我们每次需要询问所有值在[k,n+10]内,第一个下标大于r的即可。

于是我们可以通过建立权值线段树,线段树的下标是值 a i a_i ai,值是下标i,对于每个操作1,就相当于把pos位置上的数删掉了。加上一些小剪枝就可以过。

代码示例:

#include<cstdio>
#include<algorithm> 
using namespace std;
const int N = 2e5+10;
const int INF = 0x3f3f3f3f;
typedef long long ll;
struct SegmentTree{
	/*区间[l,r]内元素个数为sum*/
	int l,r,sum,mx;
	#define l(x) tr[x].l
	#define r(x) tr[x].r
	#define mx(x) tr[x].mx
}tr[N*4];
/*	a数组存放序列,b数组离散化用
	tot是离散化后不同元素个数 
*/
ll a[N],b[N];
int n,m,tot,t;
void BuildTree(int rt,int l,int r){
	/*对[l,r]建立一棵根为rt的子树*/
	l(rt) = l, r(rt) = r , mx(rt) = 0;
	if(l == r) return;
	int mid = l+r>>1;
	BuildTree(rt*2,l,mid);
	BuildTree(rt*2+1,mid+1,r);
}
void Insert(int rt,int p,int d){
	/*p位置值+1(值为p的元素的数量+1)*/
	//printf("%d %d %d %d %d\n",rt,l(rt),r(rt),p,d);
	if(l(rt) == r(rt)){
		mx(rt) = d; return;
	} 
	int mid = l(rt)+r(rt)>>1;
	if(p <= mid) Insert(rt*2,p,d);
	else Insert(rt*2+1,p,d); 
	mx(rt) = max(mx(rt*2),mx(rt*2+1));
}
ll ask(int rt,int k,int r){
	//printf("%d %d %d %d %d %d\n",rt,l(rt),r(rt),k,r,mx(rt));
	/*返回[k,n]中第一个大于r的值*/
	if(l(rt) == r(rt)){
		if(mx(rt) <= r) return -INF;
		return l(rt);
	} 
	int mid = l(rt)+r(rt)>>1, res = 0;
	if(k <= mid && mx(rt*2) > r) res = ask(rt*2,k,r);
	if(res <= 0) return ask(rt*2+1,k,r);
	return res;
}
int ans = 0;
int main(){
	scanf("%d",&t);
	while(t--){
		ans = 0;
		scanf("%d%d",&n,&m);
		BuildTree(1,1,n+10);	
		for(int i = 1;i <= n;i++){
			scanf("%lld",a+i);
			Insert(1,a[i],i);//a[i]下标为 i 
		} 
		for(int i = n+1;i <= n+10;i++) Insert(1,i,INF);
		for(int i = 1,op,pos,r,k;i <= m;i++){
			scanf("%d",&op);
			if(op == 1){
				scanf("%d",&pos);
				pos ^= ans;
				Insert(1,a[pos],INF);
			}else{
				scanf("%d%d",&r,&k);
				k ^= ans, r ^= ans;
				ans = ask(1,k,r);
				printf("%d\n",ans);
			}
		}
	}
	return 0;
}
1004 Path

测试地址
题意简述:
一共有n个点m条边和q次询问,每次询问给出一个整数k,请问在图中的所有路径中第k短的路径长度是多少?

解题思路:
刚开始思路有些不对,其实这题并不是求最短路或者A * 或怎样,据说是常见套路。
首先前m短的边就是这m条边,我们可以利用优先队列维护 路径 ,而一条路径由“起点,终点,路径长度,上一个点的编号,该边在上一个点所有边中的排名”构成。

这样每次从最小堆中弹出一条路径,该路径就是剩下所有路径中最短的一条;而且该路径只有“从当前终点出发走下一条最短边”和“返回到上一个节点par,然后从par走次短边”这两种可能会对最终答案有贡献,所以将这两条路径再压入最小堆即可,如此反复操作直至求得所有最短路径。
代码示例:

#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
struct Node{
	/*
		x:边的起点,y:终点,par:上一个点的编号,
		rk:在上一个点的所有出边中名次,d:路径长度 
	*/
	int x,y,par,rk;
	ll d;
	Node(int x,int y,ll d,int par,int rk):x(x),y(y),d(d),par(par),rk(rk){}
	bool operator < (const Node& B)const{
		return d > B.d;
	}
};
const int N = 1e5+10;
const int INF = 0x3f3f3f3f;
ll dis[N];
priority_queue<Node> q;
int t,n,m,qq,k;
int a[N],ord[N];
typedef pair<int,ll> pa;
vector<pa> G[N];//存储点的所有出边,按权值升序 
void solve(){
	int cnt = 0;
	while(!q.empty()){
		Node e = q.top();q.pop();
		dis[++cnt] = e.d;
		if(cnt > k) break;//统计出来前k个即可
		/*只有两种情况可能更新答案*/ 
		if(G[e.par].size() > e.rk+1){
			int x = e.x,y = G[e.x][e.rk+1].second , par = x, rk = e.rk+1;
			ll z = e.d - G[e.x][e.rk].first+G[e.x][rk].first;
			q.push(Node(x,y,z,par,rk));
		}
		if(G[e.y].size()) 
			q.push(Node(e.y,G[e.y][0].second,e.d+G[e.y][0].first,e.y,0));
	}
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d%d",&n,&m,&qq); k = 0;
		
		for(int i = 1;i <= n;i++) G[i].clear(),ord[i] = 0;
		while(!q.empty()) q.pop();
		
		for(int i = 1,x,y,z;i <= m;i++){
			scanf("%d%d%d",&x,&y,&z);
			G[x].push_back(make_pair(z,y));
			q.push(Node(x,y,z,0,0));
		}
		for(int i = 1;i <= n;i++) sort(G[i].begin(),G[i].end());
		for(int i = 1;i <= qq;i++) scanf("%d",a+i),k = k>a[i]?k:a[i];
		solve();//预处理 
		for(int i = 1;i <= qq;i++) printf("%lld\n",dis[a[i]]);
	}
	return 0;
}
1006 Shuffle Card

解题思路:
队友一发过了,听说是个模拟题,ccfnb。
代码示例:

#include <bits/stdc++.h>
#include <queue>
#define ll long long
using namespace std;
const int Max = 1e5 + 10;
int n,q,s;
int bk[Max];
int val[Max];
deque <int> deq;
int main(){
    scanf("%d %d",&n,&q);
    for(int i = 1; i <= n; i++){
        scanf("%d",&val[i]);
        deq.push_back(val[i]);
    }
    while(q--){
        scanf("%d",&s);
        deq.push_front(s);
    }
    while(!deq.empty()){
        if(!bk[deq.front()]){
            printf("%d ",deq.front());
            bk[deq.front()] = 1;
        }
        deq.pop_front();
    }
    return 0;
}
1007 Windows Of CCPC

题意简述:
输出CCPC,当然是2 * 2矩阵输出,此为单元。如果n > 1,那么就递归n - 1次,每次递归都把C替换成一个标准单元,把P替换为一个“反单元”(反单元即将标准单元中的C替换为P,P替换为C,即PPCP,也是2 * 2矩阵形式)作为新的矩阵。
给定n,输出结果。
解题思路:
就是简单的模拟,用数组,当然最好用putchar,否则可能会超时,还有就是n最大是10,可以输出到txt文件中肉眼观察一下是否正确再提交。

代码示例:

#include<cstdio>
#include<string>
#include<cstdlib>
#include<iostream>
using namespace std;
int t,k;
const int N = 3400;
char G[N][N];
char tmp[N][N]; 
void solve(){
    G[1][1] = G[1][2] = G[2][2] = 'C';
    G[2][1] = 'P';
    for(int i = 1;i <= k;i++){
        for(int j = 1;j <= (1<<i);j++){
            for(int z = 1;z <= (1<<i);z++){
                int r = 2*(j-1)+1;
                int c = 2*(z-1)+1;
                if(G[j][z] == 'C'){
                    tmp[r][c] = tmp[r][c+1] = tmp[r+1][c+1] = 'C';
                    tmp[r+1][c] = 'P';
                }else{
                    tmp[r][c] = tmp[r][c+1] = tmp[r+1][c+1] = 'P';
                    tmp[r+1][c] = 'C';
                }
            }
        }
        for(int j = 1;j <= (1<<i);j++)
            for(int z = 1;z <= (1<<i);z++)
              G[j][z] = tmp[j][z];
    }
    for(int i = 1;i <= 1<<k;i++){
        for(int j = 1;j <= 1<<k;j++) putchar(G[i][j]);
        puts("");
    }
}
int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    scanf("%d",&t);
    while(t--){
        scanf("%d",&k);
        solve();
    }
    return 0;
}
1008 Fishing Master

测试地址
题意简述:
一共有n条鱼,钓任意一条鱼需要花费时间k,而煮熟一条鱼需要花费 t i t_i ti 时间,这n条鱼的顺序不固定,即我们每次可以钓我们想要的那条。在钓鱼的k时间内不能中断,即我们不能在钓鱼的时间内去煮或者拿出,当然一次只能煮一条鱼。请问煮熟这n条鱼最少花费多少时间?
tip:在钓上来一条鱼后,我们可以选择等锅里的鱼煮熟(如果需要的话),或者直接去钓鱼,回来再说,因为同一时刻我们手里可以有任意多条鱼,不局限于一条。极限情况就是我们先钓完n条鱼,再一条一条煮。

解题思路:
这题细节很多,题意很多地方稍不注意就会有歧义,所以做这种题之前一定要先了解清楚题意到底是什么规则以及要求什么。搞清楚了之后算法的设计就并不难想了,是贪心策略。
如果极限情况,我们花费的最多时间就是“先把n条鱼全部钓上来,再挨个煮”,花费的时间是 s u m = n ∗ k + ∑ i = 1 n t i sum = n * k+\sum_{i = 1}^nt_i sum=nk+i=1nti,而我们能减少花费的时间,当且仅当我们在钓鱼的同时去煮鱼;而又由于钓鱼的时间段不能终止,所以就相当于我们有n-1段时间,每段时间大小为k,那么对于任意煮一条鱼的时间 t i t_i ti

  • t i t_i ti 是 k 的倍数, t i k = c n t \frac{t_i}{k} = cnt kti=cnt 那么这cnt份时间完全被利用了,即sum -= cnt * k
  • t i &gt; k t_i &gt; k ti>k,那么说明我们可以先煮几段k,而剩下的部分再考虑是等待还是继续钓鱼, t i k = c n t \frac{t_i}{k} = cnt kti=cnt,sum -= cnt * k , t i t_i ti -= cnt * k
  • 经过上面的操作之后,剩下的 t i t_i ti就都是小于k的了,那么如果此时我们还剩res段时间,则挑最大的res个 t i t_i ti来煮,这样就能最大化利用时间了,如果res = 0,则算法结束。

代码示例:

#include<cstdio>
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int tt,n;
typedef long long ll;
ll t[N],k,a[N];
ll solve(){
    ll ans = n*k;int res = n-1,tot = 0;
    int cnt = 0;
    for(int i = 1;i <= n;i++){
        if(t[i]%k == 0 && res) ans -= t[i],res -= t[i]/k;
        else a[++tot] = t[i]%k, cnt += t[i]/k;
        ans += t[i];
    }
    if(cnt >= res){
        ans -= k*res;
        return ans;
    }
    ans -= cnt*k;res -= cnt;
    sort(a+1,a+1+tot);
    for(int i = tot;i >= 1 && res;i--){
       // printf("%lld %d %lld\n",b[i],res,ans);
        ans -= a[i];res--;
    }
    return ans;
}
int main(){
    scanf("%d",&tt);
    while(tt--){
        scanf("%d%lld",&n,&k);
        for(int i = 1;i <= n;i++) scanf("%lld",t+i);
        printf("%lld\n",solve());
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷亭1213

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值