<题目链接>
A - Amity Assessment (思维 || 暴搜)
题目大意:
就是给定两个2*2的矩阵,矩阵上的每个位置给定一个初始字符,'X'代表空地,能够将其它字符在空地上移动,问你这两个矩阵经过移动后,能否变成相同的矩阵(注意,矩阵不具有翻转和对称相等的性质,即,必须所有相同坐标的字符相同,这两个矩阵才能算完全相同)。
解题分析:
就是一道思维题,但是我当时是用暴搜A的,有点浪费时间。
#include<bits/stdc++.h> using namespace std; int main(){ string a,a1,a2,b,b1,b2; cin>>a1>>a2>>b1>>b2; swap(a2[0],a2[1]);a = a1+a2; swap(b2[0],b2[1]);b = b1+b2; //得到这两个矩阵沿一个方向走的字符串(顺时针) a.erase(a.find('X'),1); b.erase(b.find('X'),1); a=a+a; if((a).find(b)!= string::npos)puts("YES"); else puts("NO"); }
我的写法,当'X'数量为1的时候,对一个2*2的矩阵进行暴搜重排,看是否能够得到第二个矩阵。
#include <bits/stdc++.h> using namespace std; #define rep(i,s,t) for(int i=s;i<=t;i++) char mpa[5][5],mpb[5][5]; bool ans=false; inline bool check(){ for(int i=1;i<=2;i++){ for(int j=1;j<=2;j++){ if(mpa[i][j]!=mpb[i][j])return false; } }return true; } map<string,int>vis; //进行状态标记 inline bool mark(){ //表示当前状态是否已经被标记过 string str; rep(i,1,2) rep(j,1,2) str+=mpa[i][j]; return vis[str]; }//记录那个状态是否走过,走过返回true inline void do_mark(){ string str; rep(i,1,2) rep(j,1,2) str+=mpa[i][j]; vis[str]=1; }//进行状态标记 inline void cg(int x1,int y1,int x2,int y2){ //对应位置字符进行替换 char c1=mpa[x1][y1],c2=mpa[x2][y2]; mpa[x1][y1]=c2,mpa[x2][y2]=c1; } void dfs(){ if(check()){ ans=true;return; } rep(i,1,2) { rep(j,1,2){ if(mpa[i][j]=='X')continue; //x不能自己动 if(i+1<=2&&mpa[i+1][j]=='X'){ cg(i,j,i+1,j); if(!mark()){ do_mark(); dfs(); } cg(i,j,i+1,j); //将这种情况还原 } if(i-1>=1&&mpa[i-1][j]=='X'){ cg(i,j,i-1,j); if(!mark()){ do_mark(); dfs(); } cg(i,j,i-1,j); } if(j-1>=1&&mpa[i][j-1]=='X'){ cg(i,j,i,j-1); if(!mark()){ do_mark(); dfs(); } cg(i,j,i,j-1); } if(j+1<=2&&mpa[i][j+1]=='X'){ cg(i,j,i,j+1); if(!mark()){ do_mark(); dfs(); } cg(i,j,i,j+1); } } } } int main(){ string str1,str2; int nx1=0,nx2=0; for(int i=1;i<=2;i++){ scanf("%s",mpa[i]+1); for(int j=1;j<=2;j++){ if(mpa[i][j]=='X')nx1++; str1+=mpa[i][j]; } } for(int i=1;i<=2;i++){ scanf("%s",mpb[i]+1); for(int j=1;j<=2;j++){ if(mpb[i][j]=='X')nx2++; str2+=mpb[i][j]; } } sort(str1.begin(),str1.end()); sort(str2.begin(),str2.end()); if(str1!=str2)return puts("NO"),0; if(nx1==0){ //no x for(int i=1;i<=2;i++){ for(int j=1;j<=2;j++){ if(mpa[i][j]!=mpb[i][j])return puts("NO"),0; } }return puts("YES"),0; } if(nx2>=2)return 0*puts("YES"); if(nx1==1){ if(check())return puts("YES"),0; //对第一个矩阵进行变换 do_mark(); dfs(); if(ans)puts("YES"); else puts("NO"); } }
B - Mischievous Mess Makers (找规律)
题目大意:
给定n、k,表示一个为1~n的初始序列,现在允许交换其中的元素k次,问你交换后,该序列所能得到的最大逆序数的个数。
解题分析:
思维,找规律,每次肯定是将第$i$个元素和第$n-i$个元素进行互换,即,序列未交换部分的最小、最大元素。$2*(n-2)+1$为交换1和n位置的元素产生的逆序数,然后每次n的数量都会-2(即未交换的序列的元素个数),所以每轮增加的逆序数都是在原来的基础上-4.
#include <bits/stdc++.h> using namespace std; typedef long long ll; int main(){ int n,k;scanf("%d%d",&n,&k); ll ans=0,num=2*(n-2)+1; while(k>0 && num>0){ ans+=num; num-=4; k--; }printf("%lld\n",ans); }
C - Enduring Exodus (二分答案)
题目大意:
给定一个01序列,0表示能够在该位置放东西,1不能,现在要在这个序列中放1个人和k头牛,问你这k头牛距这个人的位置的差值的最小最大值是多少。
解题分析:
二分答案。先预处理一下前缀和,表示前$i$个中0的个数,$O(n)$枚举这个人所在的位置,然后二分答案,枚举这个最大值,再利用前缀和判断是否符合即可。
#include <bits/stdc++.h> using namespace std; const int N = 1e5+5; int n,k; int sum[N];char s[N]; int main(){ cin>>n>>k; scanf("%s",s+1); for(int i=1;i<=n;i++){ if(s[i]=='0')sum[i]=sum[i-1]+1; else sum[i]=sum[i-1]; } int ans=1e9; for(int pos=1;pos<=n;pos++){ //枚举人所站的位置 if(s[pos]=='1')continue; int l=1,r=n; while(l<=r){ //二分答案,最远的距离 int mid = l+r>>1; int ri=min(n,pos+mid),le=max(0,pos-mid); int num=sum[ri]-sum[le-1]; if(num>=k+1){ ans=min(ans,mid);r=mid-1; }else l=mid+1; } } printf("%d\n",ans); }
D - Robot Rapping Results Report (拓扑排序+二分答案)
题目大意:
有n个人,现在给你他们之间的m关系,a b关系表示a打败了b,关系能够传递,问你最少只需要前几对关系,就能够唯一确定他们之间的等级。
解题分析:
二分枚举前x对关系,然后根据前x对关系建图,跑一遍拓扑排序,如果任何时候,队列中都只有一个元素,表示能够确定他们之间的关系。
复杂度:$O(nlogn)$
#include <bits/stdc++.h> using namespace std; template<typename T> inline void read(T&x){ x=0;int f=1;char ch=getchar(); while(ch<'0' ||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } x*=f; } #define clr(a,b) memset(a,b,sizeof a) #define pb push_back #define REP(i,s,t) for(int i=s;i<=t;i++) const int N = 1e5+5; int n,m,a[N],b[N],ind[N]; vector<int>G[N]; inline void init(){ REP(i,0,n)G[i].clear(),ind[i]=0; } bool TopoSort(){ queue<int>q; REP(i,1,n) if(!ind[i]){ q.push(i); } while(!q.empty()){ if(q.size()>1)return false; int u=q.front();q.pop(); for(int i=0;i<G[u].size();i++){ int v=G[u][i]; --ind[v]; if(ind[v]==0)q.push(v); } } return true; } bool check(int x){ init(); REP(i,1,x){ int u=a[i],v=b[i]; G[u].pb(v); ind[v]++; } return TopoSort(); } int main(){ cin>>n>>m; REP(i,1,m) read(a[i]),read(b[i]); int l=1,r=m,ans=-1; while(l<=r){ int mid=l+r>>1; if(check(mid))ans=mid,r=mid-1; else l=mid+1; } printf("%d\n",ans); }
E. Intellectual Inquiry(DP+贪心) (好题)
题目大意:
给定你能够在字符串后添加的字符数量$n$,并且给定添加的字符种类$k$(代表只能添加前$k$个字符),然后给你初始的字符串,让你对这个字符串进行构造,使得其的子串的种类数最大,并且输出这个最大值。
解题分析:
考虑如何计算真实的子串种类数。
$dp[i]$表示前$i$个字符中,子串种类数的最大值。
对于$dp[i]$,它能够接在前$i-1$所对应的所有子串后面,与之前的子串构成不同的子串,并且它还要加上前$i-1$个初始的子串数,所以$dp[i]=2*dp[i-1]$。
但是这样会发生重复计算,因为如果之前也出现了当前的字符$s[i]$,那么之前出现的字符已经计算过以这个字符为结尾的情况,所以我们需要记录每个字符之前出现的最尾端的位置$pre[s[i]]$,然后重复计算的次数就是$dp[pre[s[i]]-1]$。用$dp[i]$减去重复计算的即可得到正确答案。
考虑构造后面的$n$个字符,计算$dp[i]$的时候,$dp[i]=2*dp[i-1]-dp[pre[s[i]]-1]$,所以要使$dp[i]$尽可能的大,就要让$dp[pre[s[i]]-1]$要尽可能的小,但是因为本题涉及取模运算,所以不能直接找最小值。但是显然$dp$值递增,所以只需要找$pre$最靠前的字符就好了啦。
复杂度:$O((n+m)*k)$
#include <bits/stdc++.h> using namespace std; #define id(c) c-'a' #define REP(i,s,t) for(int i=s;i<=t;i++) const int MOD = 1e9+7; const int N = 2e6+5; typedef long long ll; ll n,k,dp[N],pos[30]; char s[N]; //dp[i]表示前i个字符的子串种类数的最大值 int main(){ cin>>n>>k;scanf("%s",s+1); int m=strlen(s+1); dp[0]=1; REP(i,1,m){ dp[i]+=dp[i-1]*2; //前i-1的真实子串种类数为dp[i-1],然后再加上之前的子串以它结尾的种类数 if(pos[id(s[i])]>0) //如果这个字符出现过了,才需要减dp[pre[c]-1],因为在pre[c]的时候,所有pre[c]-1之前的元素计算过了一遍相同的情况(即以c为最尾端的字符所构成的字符串) dp[i]-=dp[pos[id(s[i])]-1]; dp[i]%=MOD; pos[id(s[i])]=i; //更新 } REP(i,m+1,m+n){ dp[i]+=dp[i-1]*2; int c,loc=1e9; REP(j,0,k-1) if(pos[j]<loc){ //查找前k个字符最后一个字符最早出现的位置 loc=pos[j]; c=j; } if(loc>0)dp[i]-=dp[loc-1]; //同上,如果c所对应的字符之前没有出现过,那么就不需要减去重复计算的部分(因为根本就没有重复计算) dp[i]%=MOD; pos[c]=i; } printf("%lld\n",(dp[m+n]+MOD)%MOD); }