codeforces每日一题1-15
目录:
1.1093D. Beautiful Graph(DFS染色)
2.514C - Watto and Mechanism(Tire)
3.69E.Subsegments(STL)
4.25C. Roads in Berland(最短路松弛操作)
5.128B. String Problem (floyd预处理)
6.33C.Wonderful Randomized Sum(思维)
7.1292B.Aroma's Search (暴力+思维)
8.1286 A.Garland(DP)
9.1285D Dr. Evil Underscores (01Trie+DFS)
10.631C. Report(单调栈)
11.1119D. Frets On Fire (二分+排序)
12.1234D. Distinct Characters Queries(STL/线段树)
13.1073C. Vasya and Robot(二分+前缀和)
14.455A. Boredom(DP+map)
15.225C. Barcode(DP)
1093D. Beautiful Graph(DFS染色)
题意:
给你一个一个n个顶点,m条边的无向图,你可以将顶点赋值为1,2,3,现在问你有多少种赋值方案,使得任意一条边的两个顶点权值和为奇数
思路:
由于权值和要为奇数,所有一条边的两个顶点要一个为奇数,一个为偶数,所以就变成了一个染色问题
要注意图并不一定联通,所以答案为每个连通块内(2^(奇数)+2^(偶数))的乘积和-可以由组合数进行推导
如果有任意一个块无法染色,则答案为0
#include<iostream> #include<algorithm> #include<vector> using namespace std; typedef long long ll; const ll mod=998244353; const int maxn=3e5+10; vector<int> a[maxn]; int num1,num2,color[maxn],n=0,flag,two[maxn]; void pre_process() { two[0]=1; for(int i=1;i<maxn;i++) two[i]=(2*two[i-1])%mod; } void dfs(int x,int fa,int co) { if(!flag) return; color[x]=co; if(co==1) num1++; else num2++; for(int i=0;i<a[x].size();i++){ int to=a[x][i]; if(color[to]==color[x]) { flag=0;return ; } if(to!=fa&&color[to]==0) { if(co==1) dfs(to,x,2); else dfs(to,x,1); } } } int main() { int t,m,u,v; ll ans=0; scanf("%d",&t); pre_process(); while(t--){ for(int i=1;i<=n;i++) a[i].clear(),color[i]=0; num1,num2,ans=1; flag=1; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ scanf("%d%d",&u,&v); a[u].push_back(v); a[v].push_back(u); } for(int i=1;i<=n;i++){ if(!flag) break; if(!color[i]){ num1=num2=0; dfs(i,-1,1); ans*=(two[num1]+two[num2])%mod; ans%=mod; } } if(!flag) cout<<0<<endl; else cout<<ans%mod<<endl; } return 0; }
514C - Watto and Mechanism(Tire)
题意:
给n个模式串,m个匹配串,问是否有只与匹配串相差一个字符的模式串
思路:
直接上Tire,正常插入模式串
匹配时往该节点的儿子节点进行正常匹配,若能匹配成功,就输出YES
#include<iostream> #include<algorithm> #include<cstring> #include<string> using namespace std; const int maxn=1e6+10; int n,m; struct Tire{ int ch[maxn][3],val[maxn],cnt; void init() { cnt=1; memset(ch,0,sizeof(ch)); val[0]=0; } void insert(string s){ int u=0,n=s.length(); for(int i=0;i<n;i++){ int c=s[i]-'a'; if(!ch[u][c]) ch[u][c]=cnt++; u=ch[u][c]; } val[u]=cnt; } bool query(string s){ int u=0,n=s.length(),flag,k; for(int i=0;i<n;i++){ int c=s[i]-'a'; for(int j=0;j<=2;j++){ if(c==j||ch[u][j]==0) continue; int x=ch[u][j]; flag=1; for(int k=i+1;k<n;k++){ int y=s[k]-'a'; if(!ch[x][y]){ flag=0; break; } x=ch[x][y]; } if(flag&&val[x]) return true; } if(!ch[u][c]) return false; u=ch[u][c]; } return false; } }T; int main() { string temp; scanf("%d%d",&n,&m); T.init(); for(int i=1;i<=n;i++){ cin>>temp; T.insert(temp); } for(int i=1;i<=m;i++){ cin>>temp; if(T.query(temp)) cout<<"YES"<<endl; else cout<<"NO"<<endl; } return 0; }
69E.Subsegments(STL)
题意:
输出每一个长度为K的区间中出现次数不为1数的最大值
思路:
维护一个map与set
set用于维护当前所指向的区间(可以O(logn)直接取到最大值与删除容器内元素)
map用于维护区间内元素的出现个数
先将前k个元素加入set内,每个元素加入之后,通过map查询其出现次数,如果不为1则抹去,最后判断set是否为空,如果不为空则输出set内的最后一个元素(set容器内元素有序)
之后移动区间,需要删去一个元素这里要注意如果元素删去后其出现次数为1的话,要将其重新加入区间内
#include<iostream> #include<algorithm> #include<map> #include<set> using namespace std; const int maxn=1e5+10; map<int,int> cnt; set<int> s; int a[maxn]; int main() { int n,k; scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=k;i++){ cnt[a[i]]++; if(cnt[a[i]]!=1) s.erase(a[i]); else s.insert(a[i]); } if(s.empty()) cout<<"Nothing"<<endl; else cout<<*(--s.end())<<endl; for(int i=k+1;i<=n;i++){ cnt[a[i]]++; cnt[a[i-k]]--; if(cnt[a[i]]!=1) s.erase(a[i]); else s.insert(a[i]); if(cnt[a[i-k]]==1) s.insert(a[i-k]); else s.erase(a[i-k]); if(s.empty()) cout<<"Nothing"<<endl; else cout<<*(--s.end())<<endl; } return 0; }
25C. Roads in Berland(最短路松弛操作)
题意:
给你一个dis[i][j]矩阵,代表从i顶点到j顶点的最短路,再给你k条路,每次将其加入地图中,问加入后各个顶点之间最短路的和
思路:
如果加入的道路距离比原来的距离长,则不需要修改答案
如果小于原来的距离,则先将原来的距离更新,枚举起点与重点判断能否进行松弛操作
松弛操作:①dis[i][j]>dis[i][v]+w+dis[u][j]
②dis[i][j]>dis[i][u]+w+dis[v][j]
#include<iostream> #include<algorithm> #include<cstring> using namespace std; typedef long long ll; const int maxn=305; ll dis[maxn][maxn]; ll n,ans=0,m,u,v,w; void solve() { for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(dis[i][j]>dis[i][v]+w+dis[u][j]){ ans-=dis[i][j]-(dis[i][v]+w+dis[u][j]); dis[i][j]=dis[j][i]=dis[i][v]+w+dis[u][j]; } if(dis[i][j]>dis[i][u]+w+dis[v][j]){ ans-=dis[i][j]-(dis[i][u]+w+dis[v][j]); dis[i][j]=dis[j][i]=dis[i][u]+w+dis[v][j]; } } } } int main() { scanf("%lld",&n); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ scanf("%lld",&dis[i][j]); ans+=dis[i][j]; } ans/=2; scanf("%lld",&m); for(int i=1;i<=m;i++){ scanf("%lld%lld%lld",&u,&v,&w); if(dis[u][v]<=w){ cout<<ans<<" "; continue; } else{ ans-=dis[u][v]-w; dis[u][v]=dis[v][u]=w; solve(); cout<<ans<<" "; } } return 0; }
128B. String Problem (floyd预处理)
题意:
给两个字符串,以及m个从字符a变换到b的代价,问把两个字符串变得相同的最小代价
思路:
将字符串变换的代价当做一条有向边建图,之后跑floyd,得到从字符i变成j的最小代价dis[i][j]
之后遍历两个字符串的每一位,不相同的话枚举变成什么字符所需要的代价最小,如果找不到这样一个中间点使得s1[i]=ch,s2[i]=ch,则直接输出-1,最后将最小代价累加就好了
#include<iostream> #include<algorithm> #include<cstring> #define inf 0x3f3f3f3f using namespace std; const int maxn=1e5+10; int dis[27][27],mp[27][27]; char s1[maxn],s2[maxn],a1,a2,s3[maxn]; void floyd() { for(int i=0;i<26;i++) for(int j=0;j<26;j++) dis[i][j]=mp[i][j]; for(int k=0;k<26;k++) for(int i=0;i<26;i++) for(int j=0;j<26;j++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); } int main() { int n,w; cin>>s1>>s2>>n; for(int i=0;i<26;i++) for(int j=0;j<26;j++){ if(i==j) mp[i][j]=0; else mp[i][j]=inf; } for(int i=1;i<=n;i++){ cin>>a1>>a2>>w; int u=a1-'a',v=a2-'a'; mp[u][v]=min(mp[u][v],w); } floyd(); int len1=strlen(s1),len2=strlen(s2); if(len1!=len2){ cout<<-1<<endl; return 0; } int ans=0; for(int i=0;i<len1;i++){ if(s1[i]==s2[i]){s3[i]=s1[i];continue;} else{ int mini=inf,x=-1,u=s1[i]-'a',v=s2[i]-'a'; for(int j=0;j<26;j++){ if(dis[u][j]+dis[v][j]<mini) mini=dis[u][j]+dis[v][j],x=j; } if(x==-1){ cout<<"-1"<<endl; return 0; } else{ s3[i]=x+'a'; ans+=mini; } } } cout<<ans<<endl<<s3<<endl; return 0; }
33C.Wonderful Randomized Sum
题意:
给一个数组你可以把一段前缀和乘上-1,再把一个后缀和乘上-1,前缀跟后缀之间可以有重叠部分,问操作后序列的最大和是多少
思路:
我们先考虑有重叠的部分,由于乘了两次-1,所有重合的部分不变,因此答案的方案肯定不会有重叠部分
我们假设所有元素的和为sum,从反面思考假设中间的那段和为s,那么操作后数组的和为-(sum - s) + s = 2 * s - sum
所以问题就转化为找一个最大子序列问题了
#include<iostream> #include<algorithm> using namespace std; const int maxn=1e5+10; int a[maxn]; int main() { int n,sum=0; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); sum+=a[i]; } int mx,cnt=0; for(int i=1;i<=n;i++){ cnt+=a[i]; if(cnt<0) cnt=0; mx=max(mx,cnt); } cout<<2*mx-sum<<endl; return 0; }
1292B.Aroma's Search (暴力+思维)
题意:
告诉一个点的位置,之后的点按照x[i]=x[i-1]*ax+bx;,y[i]=y[i-1]*ay+by的规律分布,初始时你站在一个位置,每秒可以往四个方向移动,问你在t内最多能走过多少个点
思路:
通过数据我们可以发现,在264就已经超过了1e16也就是t的范围了,因此地图内最多只有64个点
所以我们只需要暴力求出每一个点的坐标,再枚举你所走的点的起点与终点(只往一个方向走连续的点肯定是最优)
判断距离(从初始位置走到起点的距离再加上起点到终点的距离)是否小于t即可,求出一个最大值就是答案啊
#include<iostream> #include<algorithm> #include<cmath> using namespace std; typedef long long ll; ll x[70],y[70]; ll dis(ll x1,ll y1,ll x2,ll y2) { return abs(x1-x2)+abs(y1-y2); } int main() { ll ax,ay,bx,by,xs,ys,t; int cnt=0; cin>>x[0]>>y[0]>>ax>>ay>>bx>>by; cin>>xs>>ys>>t; while(x[cnt]-xs<t&&y[cnt]-ys<t){ cnt++; x[cnt]=x[cnt-1]*ax+bx; y[cnt]=y[cnt-1]*ay+by; } int ans=0; for(int i=0;i<=cnt;i++){ for(int j=i;j<=cnt;j++){ if(j-i+1<=ans) continue; ll temp=min(dis(x[i],y[i],xs,ys),dis(x[j],y[j],xs,ys)); temp+=dis(x[i],y[i],x[j],y[j]); if(temp<=t) ans=j-i+1; } } cout<<ans<<endl; return 0; }
1286 A.Garland(DP)
题意:
有一个数组,包含乱序1-n的元素,并且有些数被从数组中拿出,现在问怎样将这些数放回才能使,相邻两数为奇偶的对数最小
思路:
定义dp[i][j][k]为到n这个位置放了j个偶数最后一位为奇数或偶数
那么转移方程为:
令t=dp[i-1][j][k]
i.奇数:dp[i][j][1]=max(dp[i][j][1],t+(k!=1))
ii.偶数:dp[i][j+1][0]=max(dp[i][j][0],t+(k!=0))
#include<iostream> #include<algorithm> #include<cstring> #define inf 0x3f3f3f3f using namespace std; const int maxn=110; int dp[maxn][maxn][3],a[maxn],n; int main() { int n,num=0; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); num=n/2; memset(dp,inf,sizeof(dp)); dp[0][0][0]=dp[0][0][1]=0; for(int i=1;i<=n;i++){ for(int j=0;j<i;j++){ for(int k=0;k<2;k++){ int t=dp[i-1][j][k]; if(a[i]){ if(a[i]%2) dp[i][j][1]=min(dp[i][j][1],t+(k!=1)); else dp[i][j+1][0]=min(dp[i][j+1][0],t+(k!=0)); } else{ for(int l=0;l<2;l++) dp[i][j+1-l][l]=min(dp[i][j+1-l][l],t+(l!=k)); } } } } printf("%d\n", min(dp[n][num][0], dp[n][num][1])); }
1285D Dr. Evil Underscores (01Trie+DFS)
题意:
给一个数组a,现在让你找到一个x使得max(x^ai)最小
思路:
建立起一颗Tire,对Tire进行DFS
如果对于当前位,即有0又有1,则对左右两个子树分别DFS并取最小值
当前位没有值的话,直接返回0
如果只有0或者1,那就对相应子树进行DFS
#include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn=2e6+10; struct Tire{ int cnt,ch[maxn][2]; void insert(int x) { int u=0; for(int i=30;i>=0;i--){ bool v=(1<<i)&x; if(!ch[u][v]){ ch[u][v]=++cnt; } u=ch[u][v]; } } int dfs(int x,int dep) { int k1=ch[x][0],k2=ch[x][1]; if(!k1&&!k2) return 0; if(k1&&!k2) return dfs(k1,dep-1); if(k2&&!k1) return dfs(k2,dep-1); return (1<<dep)+min(dfs(k1,dep-1),dfs(k2,dep-1)); } }T; int main() { int n,temp; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&temp); T.insert(temp); } cout<<T.dfs(0,30)<<endl; }
631C. Report(单调栈)
题意:
给你一个数组,并且进行若干次操作,每次操作可以将1-R,升序或者降序排列,求出经过若干次操作后的数组
思路:
对于一些操作,比如i < j并且Ri < Rj那么i操作是没有意义的,因此我们可以建立一个单调栈,将操作次数进行缩减,并得到一个以R降序的操作序列,最大的操作区间长度为len
现在将a数组的前len个元素复制到b数组,并且排序
之后从右向左向a数组中按照每次操作的排序方式填数即可
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int maxn=2e5+10; int a[maxn],b[maxn]; struct query{ int t,r; }c[maxn]; int main() { int n,m,s=0,t,r,l; scanf("%d%d",&n,&m); for(int i=0;i<n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++){ scanf("%d%d",&t,&r); while(s>0&&r>=c[s-1].r) s--; c[s].t=t,c[s].r=r,s++; } l=0,r=c[0].r,c[s].r=0,s++; for(int i=0;i<r;i++) b[i]=a[i]; sort(b,b+r); for(int i=1;i<s;i++) for(int j=c[i-1].r;j>c[i].r;j--) a[j-1]=(c[i-1].t==1)?b[--r]:b[l++]; for(int i=0;i<n;i++) cout<<a[i]<<" "; return 0; }
1119D. Frets On Fire (二分+排序)
题意:
给n个数Ai,那么矩阵Ci,j = Ai + j(j≤1e18),现在给q个询问,每次询问这个矩阵l列到j列有多少个不同的元素
思路:
首先对a数组排序去重,显然这对答案不会有影响
对于排序去重之后数组中的两个相邻元素,如果他们之间的差值大于r-l+1,那么更小的那行对答案的贡献就为r-l+1,否则就为差值
因此我们求出一差分数组,并在此排序,求出前缀和sum数组
对于每次询问,我们用二分在差分数组中找到最后一个小于r-l+1的位置pos,那么本次查询的答案就为sun[pos] + (len-pos+1)*(r-l+1)
#include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn=1e5+10; ll a[maxn],b[maxn],sum[maxn],l,r,ans[maxn]; int n,q; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); sort(a+1,a+1+n); int len=unique(a+1,a+1+n)-a-1; for(int i=1;i<len;i++) b[i]=a[i+1]-a[i]; sort(b+1,b+len); for(int i=1;i<len;i++) sum[i]=sum[i-1]+b[i]; scanf("%d",&q); for(int i=1;i<=q;i++){ scanf("%lld%lld",&l,&r); int li=1,ri=len-1; ll ret=r-l+1; while(li<=ri){ int mid=(li+ri)>>1; if(b[mid]<=r-l+1) li=mid+1; else ri=mid-1; } li--; ret+=(sum[li]+(len-li-1)*(r-l+1)); cout<<ret<<" "; } }
1234D. Distinct Characters Queries(STL/线段树)
题意:
给一个字符串,两种操作:1.进行单点修改2.询问区间l-r有多少个不同的字母
思路:
方法一:建立26棵线段树
方法二:建立26个set,每个set存相应字母出现的位置
对于修改,先在原来的字母对应的set中删除位置,再在更新的字母对应的set中插入位置
由于set中元素都为有序的,所以每次询问,在每个set中进行二分找l如果找到的元素小于等于r答案就加一
#include<iostream> #include<algorithm> #include<set> using namespace std; set<int> a[26]; string temp; int main() { cin>>temp; int n,op,pos,l,r; char ch; for(int i=0;i<temp.size();i++) a[temp[i]-'a'].insert(i+1); scanf("%d",&n); while(n--){ scanf("%d",&op); if(op==1){ cin>>pos>>ch; a[temp[pos-1]-'a'].erase(pos); temp[pos-1]=ch; a[temp[pos-1]-'a'].insert(pos); } else{ cin>>l>>r; int num=0; for(int i=0;i<26;i++){ set<int>::iterator it; it=a[i].lower_bound(l); if(it!=a[i].end()&&*it<=r) num++; } cout<<num<<endl; } } return 0; }
1073C. Vasya and Robot(二分+前缀和)
题意:
给你一串移动指令,与一个目的地,你可以对指令进行修改,每次修改的花费为,最右边的修改位置减去最左边的修改位置+1,让你求最少花费
思路:
二分答案进行判断,如何判断呢?
先用前缀和记录下每段指令中每个方向移动的次数
每次只需要判断x-(abs(ex-cntx)+abs(ey-cnty))是否为2的倍数即可,因为多出来的部分可以走相反的方向进行抵消,所以判断是否为2的倍数
其中cntx为为改变区域中在x方向上的移动距离,以x轴正方向为正,cnty类似
#include<iostream> #include<algorithm> #include<map> #include<cmath> using namespace std; const int maxn=2e5+10; map<char,int> m; int sum[4][maxn],ex,ey,n; string s; int judge(int x) { int l,r,u,d,cntx,cnty; if(x==n){ int temp=x-abs(ex)-abs(ey); if(temp>=0&&temp%2==0) return 1; return 0; } for(int i=1;i<=n-x+1;i++){ l=sum[0][i-1]+(sum[0][n]-sum[0][i+x-1]); r=sum[1][i-1]+(sum[1][n]-sum[1][i+x-1]); u=sum[2][i-1]+(sum[2][n]-sum[2][i+x-1]); d=sum[3][i-1]+(sum[3][n]-sum[3][i+x-1]); cnty=u-d,cntx=r-l; int temp=x-(abs(ey-cnty)+abs(ex-cntx)); if(temp>=0&&temp%2==0) return 1; } return 0; } int main() { scanf("%d",&n); cin>>s>>ex>>ey; int ret=-1,l=0,r=n; m['L']=0,m['R']=1,m['U']=2,m['D']=3; for(int i=1;i<=n;i++){ for(int j=0;j<4;j++) sum[j][i]=sum[j][i-1]; sum[m[s[i-1]]][i]++; } while(l<=r){ int mid=(l+r)>>1; if(judge(mid)) r=(ret=mid)-1; else l=mid+1; } cout<<ret<<endl; }
455A. Boredom(DP+map)
题意:
给一个数组,每次可以从数组中拿出一个数,但是每拿一次就得将所有x+1,x-1的数从数组中删除,问你能拿出数的最大和是多少
思路:
拿map记录下每个数出现的次数,然后对数组排序去重,之后开始DP
DP为选了第i个数的最大值
转移方程式为:dp[i]=max(dp[i],dp[i-1]+a[i]*s[a[i]]) (a[i]>a[i-1]+1)
dp[i]=max(dp[i],mx+a[i]*s[a[i]]) (mx为dp[1]-dp[i-2]中的最大值)
#include<iostream> #include<algorithm> #include<cstring> #include<map> using namespace std; typedef long long ll; const int maxn=1e5+10; map<ll,int> s; ll a[maxn],dp[maxn]; int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); s[a[i]]++; } sort(a+1,a+1+n); int len=unique(a+1,a+1+n)-a-1; dp[1]=a[1]*s[a[1]]; ll ans=dp[1],mx=0; for(int i=2;i<=len;i++){ if(a[i]>a[i-1]+1) dp[i]=max(dp[i],dp[i-1]+a[i]*s[a[i]]); dp[i]=max(dp[i],mx+a[i]*s[a[i]]); mx=max(dp[i-1],mx); ans=max(ans,dp[i]); } cout<<ans<<endl; return 0; }
225C. Barcode(DP)
题意:
有一个矩阵,每个位置是#或.,现你可以改变矩阵中某些位置,使得矩阵满足每列都相同,同一种符号的列连续出现的次数cnr必须满足,x≤cnt≤y
思路:
先求出把1-i列全部变成#的前缀和,然后定义dp[i][j]为前1-i列,最后连续一段为j所需要的最小花费
那么状态转移方程式为:f[i][0]=min(f[i][0],f[i-j][1]+(s[i]-s[i-j]))
f[i][1]=min(f[i][1],f[i-j][0]+n*j-(s[i]-s[i-j]))
#include <bits/stdc++.h> using namespace std; typedef long long ll; int a[1005][1005]; int s[1005]; char c[1005]; int f[1005][2]; int main(){ memset(f,7,sizeof(f)); f[0][0]=f[0][1]=0; int n,m,x,y; scanf("%d%d%d%d",&n,&m,&x,&y); for(int i=1;i<=n;i++){ scanf("%s",c+1); for(int j=1;j<=m;j++){ a[i][j]=(c[j]=='#'?1:0); } } for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ s[i]+=a[j][i]; } s[i]+=s[i-1]; } for(int i=x;i<=m;i++){ for(int j=x;j<=y;j++){ if(i-j>=0){ f[i][0]=min(f[i][0],f[i-j][1]+(s[i]-s[i-j])); f[i][1]=min(f[i][1],f[i-j][0]+n*j-(s[i]-s[i-j])); } } } printf("%d\n", min(f[m][0],f[m][1])); return 0; }