AtCoder Beginner Contest 429(ABCDEF)
前言
感冒了状态真不行,c和d卡了半天,昨天晚上又发烧了,现在嗓子疼得一批……
一、A - Too Many Requests
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) for(auto x:a) cout<<x<<" ";cout<<endl
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int n,m;cin>>n>>m;for(int i=1;i<=n;i++){if(i<=m){cout<<"OK"<<endl;}else{cout<<"Too Many Requests"<<endl;}}
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
这个题还是很简单的,就是分小于等于m和大于m讨论一下即可。
二、B - N - 1
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) for(auto x:a) cout<<x<<" ";cout<<endl
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int n,m;cin>>n>>m;vector<int>a(n+1);int sum=0;for(int i=1;i<=n;i++){cin>>a[i];sum+=a[i];}for(int i=1;i<=n;i++){if(sum-a[i]==m){cout<<"Yes"<<endl;return ;}}cout<<"No"<<endl;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
这个题就是先统计一下整体累加和,再逐个判断移除每个位置后的累加和是否等于m即可。
三、C - Odd One Subsequence
以前的c有这么难吗,调了半天才调出来……
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) for(auto x:a) cout<<x<<" ";cout<<endl
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int n;cin>>n;vector<int>a(n+1);for(int i=1;i<=n;i++){cin>>a[i];}vector<ll>pre(n+1);ll sum=0;ll ans=0;for(int i=1;i<=n;i++){ll cnt=pre[a[i]];ll diff=i-1-cnt; ans+=cnt*diff;ll cur=(cnt-1)*cnt/2;if(sum>cur){ans+=sum-cur;}pre[a[i]]++;if(pre[a[i]]>=2){sum-=(cnt-1)*cnt/2;sum+=(pre[a[i]]-1)*pre[a[i]]/2;}}cout<<ans<<endl;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
对于某个位置上的数x,能形成的合法三元组的个数,可以从前面所有和x相同的数里选一个,再从前面所有和x不同的数里选一个组成,也可以从前面每个和x不同的数里都选出两个组成。第一种情况好求,重点是第二种情况。那么可以考虑维护出前缀中从每种数中选两个的总方案数,那么每次只需要扣去从当前数x中选两个的情况,之后再更新即可。
byd其实根本不用这么麻烦,直接遍历一遍统计词频,再枚举从当前数里选两个,再从和当前数不同的所有数里选一个即可。反正a[i]小于等于n,根本没必要写的这么麻烦……
四、D - On AtCoder Conference
感觉这题也写复杂了……
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) for(auto x:a) cout<<x<<" ";cout<<endl
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;int bs(ll v,int l,vector<ll>&a)
{int r=a.size();int m;int ans=0;while(l<=r){m=l+r>>1;if(a[m]>=v){ans=m;r=m-1;}else{l=m+1;}}return ans;
}void solve()
{ll n,m,c;cin>>n>>m>>c;vector<ll>a(n+1);for(int i=1;i<=n;i++){cin>>a[i];} sort(a.begin()+1,a.end());vector<ll>cnts;vector<ll>pos;for(int i=1;i<=n;i++){int j=i+1;while(j<=n&&a[j]==a[i]){j++;}cnts.push_back(j-i);pos.push_back(a[i]);i=j-1;}int len=cnts.size();for(int i=0;i<len;i++){cnts.push_back(cnts[i]);}len<<=1;for(int i=1;i<len;i++){cnts[i]+=cnts[i-1];}ll ans=0;for(int i=0;i<len/2;i++){int p=bs(c+cnts[i],i,cnts);ll cur=cnts[p]-cnts[i];if(i+1<len/2){ll cnt=pos[i+1]-pos[i];ans+=cur*cnt;}else{ll cnt=m-(pos[i]-pos[0]);ans+=cur*cnt;}}cout<<ans<<endl;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
因为这个问题是环,所以还是考虑将其多抄一遍转化成链,然后存一下每个位置的人数并求前缀和。那么对于当前出发点i,就可以直接去二分大于等于cnts[i]+c的最左位置即可。之后注意到m非常大,所以肯定不能直接开m长度的数组,所以还是要考虑对其进行离散化,那么就把下标和m有关转化成和n有关了。
代码就是先从小到大排序,然后统计每个位置上的人数,多抄一遍并统计前缀和。之后注意到,对于离散化后的相邻两个有人的位置,中间所有没人的位置的答案都和前一个有人的位置一样,所以还需要多记录一个pos表存每个有人的位置。所以答案就是相邻两个有人位置的间距乘以从这个位置开始经过的人数即可。
五、E - Hit and Away
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) for(auto x:a) cout<<x<<" ";cout<<endl
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;const int MAXN=2e5+5;int n,m;vector<vector<int>>g(MAXN);//from[u]:离u最近和第二近的安全点
vector<array<int,2>>from(MAXN,{-1,-1});
//dis[u]:离u最近和第二近的安全点的距离
vector<array<int,2>>dis(MAXN,{-1,-1});void solve()
{cin>>n>>m;for(int i=1,u,v;i<=m;i++){cin>>u>>v;g[u].push_back(v);g[v].push_back(u);}string s;cin>>s;s=" "+s;queue<pii>q;for(int i=1;i<=n;i++){if(s[i]=='S'){q.push({i,0});//自己from[i][0]=i;dis[i][0]=0;}}while(!q.empty()){auto cur=q.front();q.pop();int u=cur.first;int o=cur.second;for(auto v:g[u]){//没有最近点if(dis[v][0]==-1){dis[v][0]=dis[u][o]+1;from[v][0]=from[u][o];q.push({v,0});}//没有次近点且v的最近点和次近点不相同else if(dis[v][1]==-1&&from[v][0]!=from[u][o]){dis[v][1]=dis[u][o]+1;from[v][1]=from[u][o];q.push({v,1});}}}for(int i=1;i<=n;i++){if(s[i]=='D'){cout<<dis[i][0]+dis[i][1]<<endl;}}
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
这个多源bfs太妙了……
考虑设置from和dis表,分别表示离u节点最近和次近的节点以及距离。那么初始将所有安全节点的from[i][0],即最近点设置成自己,dis[i][0]设置为0,然后把(i,0)这个状态放入队列。之后定义状态(u,o)为节点u作为某个点的最近点或次近点,然后每次去看当前点的相邻节点v。若v还没有设置最近点,那么v的最近点就是u当前状态的点,否则若v没设置次近点,且v的最近点和次近点不同,那么v的次近点就是u当前状态的点。那么最后只需要输出每个危险点到其最近点和次近点的距离之和即可。
六、F - Shortest Path Query
太难了……没有任何套路,就是纯难……
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) for(auto x:a) cout<<x<<" ";cout<<endl
#define INF 1e9
#define INFLL 1e18
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;const int MAXN=2e5+5;int g[3][MAXN];struct matrix
{int a[3][3];matrix(){for(int i=0;i<3;i++){for(int j=0;j<3;j++){a[i][j]=INF;}}}matrix operator*(const matrix&x){matrix ans;for(int i=0;i<3;i++){for(int j=0;j<3;j++){for(int k=0;k<3;k++){ans.a[i][k]=min(ans.a[i][k],a[i][j]+x.a[j][k]);}}}return ans;}//矩阵和线段树
}A[MAXN],res[MAXN<<2];void up(int i)
{//注意矩阵乘法从右往左res[i]=res[i<<1|1]*res[i<<1];
}void build(int l,int r,int i)
{if(l==r){res[i]=A[l];}else{int m=l+r>>1;build(l,m,i<<1);build(m+1,r,i<<1|1);up(i);}
}void change(int jobi,int l,int r,int i)
{if(l==r){res[i]=A[l];}else{int m=l+r>>1;if(jobi<=m){change(jobi,l,m,i<<1);}else{change(jobi,m+1,r,i<<1|1);}up(i);}
}matrix init(int i)
{int c0=g[0][i];int c1=g[1][i];int c2=g[2][i];matrix ans;ans.a[0][0]=c0?1:INF;ans.a[1][0]=c0&&c1?2:INF;ans.a[2][0]=c0&&c1&&c2?3:INF;ans.a[1][1]=c1?1:INF;ans.a[0][1]=c0&&c1?2:INF;ans.a[2][1]=c2&&c1?2:INF;ans.a[2][2]=c2?1:INF;ans.a[1][2]=c2&&c1?2:INF;ans.a[0][2]=c2&&c1&&c0?3:INF;return ans;
}void solve()
{int n;cin>>n;string s;for(int i=0;i<3;i++){cin>>s;s=" "+s;for(int j=1;j<=n;j++){g[i][j]=s[j]=='.';}}//因为只有三行,所以观察可以发现,最短路不可能向左走//所以最优路径可以通过从左往右dp一遍算出//定义dp[i][j]为从(1,1)开始走到(i,j)的最小步数//所以有//dp[1][j+1]=min(dp[1][j]+1,dp[2][j]+2,dp[3][j]+3)//dp[2][j+1]=min(dp[1][j]+2,dp[2][j]+1,dp[3][j]+2)//dp[3][j+1]=min(dp[1][j]+3,dp[2][j]+2,dp[3][j]+1)//所以这个可以描述成矩阵乘法,所以dp[n]=An*……*A1*dp[1]//若一个位置有墙,那么可以将矩阵对应位置改成无穷大,不让其转移//因为涉及单点修改,所以可以考虑用线段树维护矩阵乘法的结果//初始化每列的矩阵for(int i=1;i<=n;i++){A[i]=init(i);}build(1,n,1);int q;cin>>q;int r,c;while(q--){cin>>r>>c;r--;g[r][c]^=1;A[c]=init(c);change(c,1,n,1);int ans=res[1].a[2][0];cout<<(ans!=INF?ans-1:-1)<<endl;}
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
因为只有三行,所以观察可以发现最短路不可能向左走,所以最优路径可以通过从左往右dp一遍得到。定义dp[i][j]为从(1,1)开始走到(i,j)的最小步数,那么可以得到注释中写的转移公式。所以这个转移过程可以描述为矩阵乘法,所以就有dp[n]=A[n]*A[n-1]*……*A[1]*dp[1]。而若一个位置有墙,那么在矩阵对应位置改成无穷大即可。又因为每次修改是单点修改,所以可以考虑用线段树维护矩阵乘法的结果。
所以init函数就是通过当前第i行的状态去构造相应矩阵,注意线段树up时矩阵是从右往左乘的即可。
总结
先把身体养好再说吧……
