需要笔试辅导的可以私我,ACM金牌退役选手,已拿腾讯和阿里offer,可以视频会议1V1辅导,混个脸熟,不收钱。
num1
题意:正方形划分为8个部分。
思路:直接判断就行。代码写得不好看就不发了。
num2
题意:给定01矩阵,有一些连通的1,用0分割,现在你最多可以把其中1个1替换位置,问替换之后的最大连通块是多大。
思路:为了保证做法不会出错,我们得考虑:
- 如果只有一个连通块,那么它就是答案;
- 多个连通块(最多为4)是否可以移动内部的某个1来连接;
- 多个连通块是否可以通过外部的某个1来连接。
对于2,3种情况,我们把他统一为,如果多个连通块中间只被一个空格隔开,那么答案就是这几个连通块大小之和。同时如果这几个连通块之外还有连通块,那么答案+1。+1是表示用外部的连通块来“填”这个空位,否则表示内部的1换了个位置达到了连接的效果.
综上,我的代码用并查集实现,这样可以得到每个连通块的编号、大小; 移动1,等效于枚举被填的那个位置,然后看4周会不会贡献,贡献并不是独立的,所以还得用并查集判重;最后判定是否+1;
----代码写得比较长,不要嫌弃----
#include<bits/stdc++.h> using namespace std; const int maxn=410; int mp[maxn][maxn],fa[maxn*maxn]; //mp为01矩阵,fa表示点所在的集合 int id[maxn][maxn],ans,num[maxn*maxn],cnt; //id表示矩阵对应的id,num表示集合的大小。 int tfa[maxn]; int dx[4]={1,-1,0,0}; //4个方向 int dy[4]={0,0,1,-1}; int find(int x) //并查集 { if(x==fa[x]) return x; return fa[x]=find(fa[x]); } void Merge(int x1,int y1,int x2,int y2) //合并相邻的1 { if(mp[x1][y1]&&mp[x2][y2]){ //如果都为1,才合并。 int w=find(id[x1][y1]); //得到id对应的集合。 int v=find(id[x2][y2]); if(w!=v) { //集合不同就合并。 num[v]+=num[w]; //大小合并 ans=max(ans,num[v]); fa[w]=fa[v]; //集合id合并。 cnt--; // 集合的数量-1 } } } void add(int i,int j,int &tmp,int &sum) //把(i,j)代表的集合加进去,同时记录已经加进去的集合,用tfa[]表示,这里用来防止重复加。 { int d=find(id[i][j]),F=1; for(int i=1;i<=tmp;i++){ if(tfa[i]==d){ //tfa已经存在,则不再加,那么break F=0; break; } } if(!F) return ; //存在,返回。 tmp++; sum+=num[d]; tfa[tmp]=d; } int main() { int N,M; scanf("%d%d",&N,&M); for(int i=1;i<=N;i++){ for(int j=1;j<=M;j++){ scanf("%d",&mp[i][j]); id[i][j]=(i-1)*M+j; if(mp[i][j]) { num[id[i][j]]=1; //集合初始化大小为1. ans=1; cnt++; //连通块个数+1。 (cnt用于后来判定是否可以取连通块之外的+1) } fa[id[i][j]]=id[i][j]; } } for(int i=1;i<=N;i++){ for(int j=1;j<=M;j++){ if(i>1) Merge(i-1,j,i,j); if(j>1) Merge(i,j-1,i,j); } } if(cnt>=2){ //cnt=1的时候没必要合并。 for(int i=1;i<=N;i++){ for(int j=1;j<=M;j++){ if(mp[i][j]) continue; // 只枚举0的位置,然后拿其他1来‘填’。 int tmp=0,sum=0; for(int k=0;k<4;k++){ //枚举上下左右是否可以通过这个0来连接。 int nowx=i+dx[k],nowy=j+dy[k]; if(nowx>=1&&nowx<=N&&nowy>=1&&nowy<=M){ if(mp[nowx][nowy]){ add(nowx,nowy,tmp,sum); } } } if(tmp==cnt) ans=max(ans,sum); else ans=max(ans,sum+1);//表示这上下左右之外的连通块可以贡献一个1来填,所以+1; } } } printf("%d ",ans); return 0; }
num3
题意:普通的01背包,但是现在体积可能为负数。
思路:一眼题,因为体积为负数,表示我们的体积会变大,这种情况,我们把它变为相反数即可--------表示我一开始就装进去,那么在跑背包的时候如果选择了它的相反数这个物体,代表把它移除。(而这个时候所有的物体都是正他体积,跑01背包就行。)
注:如果你不会01背包的话,那么你在学习的时候得注意,第二个for倒序遍历,这样防止重复更新答案。或者用二维的dp。
#include<bits/stdc++.h> using namespace std; const int maxn=410; int c[maxn],v[maxn],ans; int dp[40010]; int main() { int N,M; scanf("%d%d",&N,&M); for(int i=1;i<=N;i++){ scanf("%d%d",&c[i],&v[i]); if(c[i]<=0){ //负的物体取反 ans+=v[i]; M-=c[i]; c[i]=-c[i]; v[i]=-v[i]; } } for(int i=1;i<=N;i++) //01背包 for(int j=M;j>=c[i];j--) dp[j]=max(dp[j],dp[j-c[i]]+v[i]); for(int i=0;i<=M;i++) { dp[M]=max(dp[M],dp[i]); } printf("%d ",ans+dp[M]); return 0; }
num4
位运算和gcd
容斥原理,求所有m的集合,在这个集合的所有数,求一下lcm(最小公倍数),如果|集合|是奇数,ans+=n/lcm,否则ans-=n/lcm 。
具体的:先从简单的问题走起,问1到10里面多少个2的倍数-------> ans=10/2=5;
问1到10里面多少个5的倍数-------> ans=10/5=2;
问1到10里面多少个2或者5的倍数-------> ans=10/5+10/2-10/10=6;
其实也就是容斥的一种特殊情况----抽屉原理。
所以这里用二进制枚举所有情况(对应1到F),二进制对应的1,表示选择、0表示不选择;然后把1对应的位(if(i&(1<<j)))求LCM;算贡献的时候取决于1的个数。
#include <bits/stdc++.h> using namespace std; #define LL long long LL a[40]; int main(){ LL N,M,ans=0,gd; scanf("%d%d",&N,&M); for(int i=1;i<=M;i++) { scanf("%d",&a[i-1]); } LL F=(1<<M)-1; for(int i=1;i<=F;i++){ LL cnt=0; for(int j=0;j<M;j++){ if(i&(1<<j)){ cnt++; if(cnt==1) gd=a[j]; else gd=gd*a[j]/(__gcd(a[j],gd)); } } if(cnt&1){ ans+=N/gd; } else ans-=N/gd; } printf("%d ",ans); return 0; }