7.21号暑假集训

作者分享了参与编程竞赛的经历,包括成功晋级国赛的喜悦以及在比赛中的表现。文章详细讨论了几道算法题的解题思路,涉及并查集、异或操作、动态规划和图论问题,如最长路、最大食物链计数等,并提到了优化效率和理解题意的重要性。
摘要由CSDN通过智能技术生成

对了昨天笔记忘记写了,我robocom进国赛了...省赛进国赛的最后几名,我这运气也太好了。哭死...

今天打了场div2,还做了2题就没有了.....感觉自己的效率真的需要提升了...

要注重效率啊,不要太关心时间,要达到忘我的境界,但事实上做不到....

还有今天牛客我换了个头像,改了个名字,确实好开心啊。

1个小时之后还要打div4,闲话少说吧。

CF882:

A:

思路还是很简单的,自己手玩一下样例就知道怎么做了,即删除n-k个最大的区间即可

B:这题我想了好久,我是真的菜啊....

其实就是没考虑&的性质。a&b<=a+b,这是一定成立的!

所以我们从前到后&,如果&到0就区间就答案就++,新开一个区间。

如果最后我们统计的区间个数为0的话,我们输出1即可(a&b<=a+b)!!

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<stack>
#include<deque>
#include<vector>
#include<map>
#include<set>
#include <utility>
#include <list>
using namespace std;
typedef  long  long ll ;
typedef  unsigned long  long ull ;
#define pii pair<int,int>
const int inf = 0x3f3f3f3f;//106110956
inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = (x<<1) + (x<<3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}
void print(__int128 num) {
	if(num) {
		print(num/10);
		putchar(num%10+'0');
	}
}
ll ex_gcd(ll a,ll b,ll& x,ll& y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}

	ll d=ex_gcd(b,a%b,y,x);
	y=y-a/b*x;
	return d;
}
int t;
int n;
int main(){
	scanf("%d",&t);
	while(t--){
		int ans;
		int cnt=0;
		int flag=0;
		ll x;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%lld",&x);
			if(flag==0){
				ans=x;
				flag=1;
				if(ans==0){
					flag=0;
					cnt++;
				}
			}else{
				ans=ans&x;
				if(ans==0){
					cnt++;
					flag=0;
				}
			}
		}
		
		if(ans==0){
			printf("%d\n",cnt);
		}else{
			if(cnt==0){
				printf("1\n");
			}else{
				printf("%d\n",cnt);
			}
		}
	}


	return 0;
}

 

C:这题考察异或..我还真没有啥思路...样例都没看出来怎么出现的

然后去补题了..

我们发现后缀数组对答案没有影响,

假设数组为 1 2 3 4 5 6,假设1和2异或为我们的答案,这时我们可以构造1 2 3 4 5 6 (3^4^5^6)

(3^4^5^6)和3.4.5.6异或为0,0异或任何数都为0

所以这题可以转换为求最大的一段异或和

我们发现数组的范围为2^8,所以异或的最大值为2^8,因为异或是按位异或的(最大的可能也才是8个1相连)即256

我们设dp[i][j]为第i个数的j状态,dp[i][j]=max(dp[i][j],dp[i-1][j^a[i]])因为我们是从前往后异或的,并且j^a[i]^a[i]=j

i的范围为1到n,因为后面也是等价的!!,仔细想想是一样的。

j的范围为0到256

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<stack>
#include<deque>
#include<vector>
#include<map>
#include<set>
#include <utility>
#include <list>
using namespace std;
typedef  long  long ll ;
typedef  unsigned long  long ull ;
#define pii pair<int,int>
const int inf = 0x3f3f3f3f;//106110956
inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = (x<<1) + (x<<3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}
void print(__int128 num) {
	if(num) {
		print(num/10);
		putchar(num%10+'0');
	}
}
ll ex_gcd(ll a,ll b,ll& x,ll& y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}

	ll d=ex_gcd(b,a%b,y,x);
	y=y-a/b*x;
	return d;
}
int t;
int n;
int dp[100005][260];
int a[100005];
int main(){
	scanf("%d",&t);
	while(t--){
		int ans=0;
		scanf("%d",&n);
		
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			ans=max(ans,a[i]);
			for(int j=0;j<256;j++){
				dp[i][j]=0;
			}
			dp[i][a[i]]=1;
		}
		
		for(int i=1;i<=n;i++){
		
			for(int j=0;j<256;j++){
				dp[i][j]=max(dp[i][j],dp[i-1][j^a[i]]);
				
					if(dp[i][j]==1){
					ans=max(ans,j);
					}
			}
			
		}
		
		printf("%d\n",ans);
	}


	return 0;
}
 

最近洛谷题单刷到了图的基本应用了,前面后刷了刷并查集,主要是扩展域并查集,真的好简单...比带权的好多了

P1621 集合

这题还是比较有意思的

首先打p到b之间的素数表,然后暴力即可...(我还用了欧拉筛法...)

然后我们枚举素数,然后找到第一个c满足:  c*(素数)>=a,然后我们逐次增加c,如果(c+1)*(素数)<=b,我们就merge( c*(素数),(c+1)*(素数))即可

然后merge即可

然后学了存图...链式前向星,自我感觉还是比较简单的...(bushi)

P3916 图的遍历

这题挺有意思的...显示内存超了..我还找数组是不是开的太大了..

结果是dfs太多层的原因....

这题就是反向建边,从n到1循环

P1113 杂务

这题是动规.....

因为我们要做这件事情之前的所需要的时间一定在这件事情之前,对于事情i和它的需要的事情j

dp[i]=max(dp[i],dp[j]+a[i])

另外一种做法:拓扑排序

代码还是很好理解的

但是最好一开始入队列的时候的for循环放在while的外面

// deg是入度,在存图的时候需要录入数据
// A是排序后的数组
int deg[MAXN], A[MAXN];
bool toposort(int n)
{
    int cnt = 0;
    queue<int> q;
    for (int i = 1; i <= n; ++i)
        if (deg[i] == 0)
            q.push(i);
    while (!q.empty())
    {
        int t = q.front();
        q.pop();
        A[cnt++] = t;
        for (auto to : edges[t])
        {
            deg[to]--;
            if (deg[to] == 0) // 出现了新的入度为0的点
                q.push(to);
        }
    }
    return cnt == n;
}

还有:注意我们需要一个a数组来存储每一件事情的花费,不能直接用dp来存,否则就是它所有需要做的事情的总和了

dp[son[u][j]]=max(dp[son[u][j]],a[son[u][j]]+dp[u]);

P4017 最大食物链计数

这题我们需要开2个数组来统计出度和入度,入度为0的点即最佳生产者,出度为0的点为最佳消费者

状态转移方程

dp[son[u][j]]=(dp[son[u][j]]+dp[u])%mod;

P1807 最长路:

这一题感觉就比较难了....

我们要统计1到n的最长的路的距离

1的入度一定为0,但是还有其他点的入度为0

如果其他点不入队列,只有1入队列的话,那么他们延伸出来的点的入度永远大于 0

同理如果入队列的话,因为他们本身是无法到达的点,所以根本不可能会延伸到其他地方

所以我们需要单独处理其他入度为0的点....然后就很奇怪了...for循环必须在while外面,不然是不对的...不太懂.啊....

for(int i=2;i<=n;i++){
		if(in[i]==0){
			q.push(i);
		}
	}
			while(!q.empty()){
				int u=q.front();
				q.pop();
				for(int j=0;j<son[u].size();j++){
					int k=son[u][j];
					in[k]--;
					if(in[k]==0){
						q.push(k);
					}
				}		
			}

昨天打了场div4,直接拉跨了....排名7000多,对内大腿比我多了一题,排名3000多...

前几道题还是比较顺利的...然后D题..我没有太看懂题目的意思就开始做了....然后觉得这题好难....
不太会啊...然后又读了一边题意,然后明白了

D:

要使答案最小,我们只需求出最长的满足条件的序列,然后长度-序列的长度就是答案了,还是很好写的。。。但是因为数组少开了1倍,然后直接wa了2法...一开始我还以为读的太慢了,用了快速读,结果...

E:

这题的题意我感觉读了得10分钟,一直读不明白,然后在回去读题..一直往返..

最后好歹读明白了,

只要求满足(x1+w)^2+(x2+w)^2+...+(xn+w)^2==c的w即可

原来以为T组数据不算复杂度,最近听别人说算复杂度..昨天这场比赛就以为T算复杂度了...

事实上,T是不算复杂度的’

The sum of nn over all test cases doesn't exceed 2⋅10^5.

T不关系复杂度,但是n关系复杂度

另外这题我移项了,然后就是n*w^2+2*w(x1+x2+x3+..+xn)=c-(x1^2+x2^2...+xn^2)了

然后我们二分答案,但是出现了越界的现象..long long 越界...

然后我又开了double...结果也不行,浮点数二分很烦....一直调不对.....

是我浮点数二分写错了.....尴尬死了....

while(right-left>=1e-4){
			double mid=(left+right)/2;
			if(mid*mid*n+2*sum*mid>c){
				right=mid;
			}else{
				left=mid;
			}
		}

正确思路:我们从1到n模拟计算即可..

如果出现了sum>c的情况就返回即可

a[i]范围为1e4,c的范围为1e18,所以我们的右边界为1e9即可

第一次mid=1e9/2,直接不行..

然后就逐渐缩小了..这个精度还是很烦人的

bool check(ll mid){
	
	ll ans=0;
	for(int i=1;i<=n;i++){
		ans=ans+(a[i]+2*mid)*(a[i]+2*mid);
		if(ans>c)return 0;
	}
	return 1;
	
}

F:

做完E题还有20分钟左右...感觉自己就做不出来...就没怎么思考了.

以为是gcd结果不是...

看我队友写的也挺麻烦的...

然后直接看榜一代码:真的6,思路很清晰

我们a[i]为一开始能跳i步的青蛙的个数,vis[j]就是在j设置陷阱能抓的青蛙的数量

如果x>n直接跳过即可

	for(int i=1;i<=n;i++){
			scanf("%lld",&x);
			if(x>n)continue;
			a[x]++;
		} 

然后我们枚举步数,在枚举位置 

注意下面的双层循环的复杂度不为O(n^2)而是O(nln(n))调和级数

	for(int i=1;i<=n;i++){
			for(int j=i;j<=n;j=j+i){
				vis[j]=vis[j]+a[i];
				ans=max(ans,vis[j]);
			}
		}

真的不太好想.... 

G:       我感觉这个G比F简单多了..

读懂题之后思路大体上就有了

如果罗盘在(x,y)如果使得罗盘不坏

即        有横坐标与x相同的点,有纵坐标与y相同的点,或者a-x/b-y=abs(1)

得x-y==a-b或者x+y==a+b,不存在两者同时成立的情况除非a,b和x,y重合

假设有k个互相满足上面情况答案为Ak2,不是Ck2

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<stack>
#include<deque>
#include<vector>
#include<map>
#include<set>
#include <utility>
#include <list>
using namespace std;
typedef  long  long ll ;
typedef  unsigned long  long ull ;
#define pii pair<int,int>
const int inf = 0x3f3f3f3f;//106110956
inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = (x<<1) + (x<<3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}
void print(__int128 num) {
	if(num) {
		print(num/10);
		putchar(num%10+'0');
	}
}
ll ex_gcd(ll a,ll b,ll& x,ll& y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}

	ll d=ex_gcd(b,a%b,y,x);
	y=y-a/b*x;
	return d;
}
int t;
int n;
map<ll,ll>mp1,mp2,mp3,mp4;
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		mp1.clear();
		mp2.clear();
		mp3.clear();
		mp4.clear();
		ll x,y;
		ll ans=0;
		for(int i=1;i<=n;i++){
			scanf("%lld%lld",&x,&y);
		
			mp1[x-y]++;
			mp4[x+y]++;
			mp2[x]++
			mp3[y]++;
		}
		
		for(auto it=mp1.begin();it!=mp1.end();it++){
			ans=ans+it->second*(it->second-1);
		}
		
		for(auto it=mp2.begin();it!=mp2.end();it++){
			ans=ans+it->second*(it->second-1);
		}
		
		for(auto it=mp3.begin();it!=mp3.end();it++){
			ans=ans+it->second*(it->second-1);
		}
		
		for(auto it=mp4.begin();it!=mp4.end();it++){
			ans=ans+it->second*(it->second-1);
		}
		
		printf("%lld\n",ans);
		
	}


	return 0;
}


H:

这题也是比较简单的...

但就是题目难理解...带权并查集的模板题吧

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<stack>
#include<deque>
#include<vector>
#include<map>
#include<set>
#include <utility>
#include <list>
using namespace std;
typedef  long  long ll ;
typedef  unsigned long  long ull ;
#define pii pair<int,int>
const int inf = 0x3f3f3f3f;//106110956
inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = (x<<1) + (x<<3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}
void print(__int128 num) {
	if(num) {
		print(num/10);
		putchar(num%10+'0');
	}
}
ll ex_gcd(ll a,ll b,ll& x,ll& y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}

	ll d=ex_gcd(b,a%b,y,x);
	y=y-a/b*x;
	return d;
}
int t; 
ll fa[200005];
ll ra[200005];
int n,m;
int find(int x){
	if(x!=fa[x]){
		int t=fa[x];
		fa[x]=find(fa[x]);
		ra[x]=ra[x]+ra[t];
	}
	return fa[x];
}
void merge(int x,int y,int w){
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy){
		fa[fx]=fy;	
		ra[fx]=w+ra[y]-ra[x];
	}
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++){
			fa[i]=i;
			ra[i]=0;
		}
		int x,y,z;
		int flag=0;
		for(int i=1;i<=m;i++){
			scanf("%d%d%d",&x,&y,&z);
			if(flag)continue;
			int fx=find(x);
			int fy=find(y);
			if(fx!=fy){
				merge(x,y,z);
			}else{
				if(ra[x]==ra[y]+z){
					continue;
				}else{
					flag=1;
				}
			}
		}
		
		if(flag==0){
			printf("YES\n");
		}else{
			printf("NO\n");
		}
	}
	
	
	return 0;
}


P1983 [NOIP2013 普及组] 车站分级

又要开始拓扑排序了...

这题做的时候没思路...遂看标签,看完标签是拓扑排序,然后想了一会没思路....去看题解了

我们用vis[i][j]表示有一条从i到j的边

在m次读入中,我们用is数组来标记是否有没有读入,我们我们从a[i]到a[s]循环(因为起点和终点是在a[1]和a[s]之外的并且我们不知道起点和终点的坐标,所以我们不从1到n循环),s是这组一共有多少站点,如果没有标记,然后我们将其与我们读入的点建立有向边,如果有边就不用建边了..

我们设入度为0的点的深度为1,然后拓扑排序即可,我们求最大深度即可;

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<stack>
#include<deque>
#include<vector>
#include<map>
#include<set>
#include <utility>
#include <list>
using namespace std;
typedef  long  long ll ;
typedef  unsigned long  long ull ;
#define pii pair<int,int>
const int inf = 0x3f3f3f3f;//106110956
inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = (x<<1) + (x<<3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}
void print(__int128 num) {
	if(num) {
		print(num/10);
		putchar(num%10+'0');
	}
}
ll ex_gcd(ll a,ll b,ll& x,ll& y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}

	ll d=ex_gcd(b,a%b,y,x);
	y=y-a/b*x;
	return d;
}
struct Node{
	int to,next;
}edge[1000005];
int cnt;
int head[10005];
void add(int u,int v){
	cnt++;
	edge[cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
int vis[1005][1005];
int in[10005];
int n,m;
int is[10005];
int a[10005];
int ans=1;
int dep[1005];
int main(){
	
	scanf("%d%d",&n,&m);
	
	for(int i=1;i<=m;i++){
		memset(is,0,sizeof(is));
		memset(a,0,sizeof(a));
		int s;
		scanf("%d",&s);
		
		for(int j=1;j<=s;j++){
			scanf("%d",&a[j]);
			is[a[j]]=1;
		}
		
		for(int j=a[1];j<=a[s];j++){
			if(is[j]==0){
				for(int k=1;k<=s;k++){
					if(vis[j][a[k]]==0){
						vis[j][a[k]]=1;
						add(j,a[k]);
						in[a[k]]++;
					}
				}
			}
		}
	}
	
	queue<int>q;
	for(int i=1;i<=n;i++){
		if(in[i]==0){
			q.push(i);
			dep[i]=1;
		}
	}
	
	while(!q.empty()){
		int u=q.front();
		q.pop();
	
		for(int i=head[u];i;i=edge[i].next){
			int v=edge[i].to;
			dep[v]=dep[u]+1;
			ans=max(ans,dep[v]);
			in[v]--;
			if(in[v]==0){
				q.push(v);
			}
		}
	}
	
	printf("%d\n",ans);

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值