AtCoder 题目集1
AtCoder 题目集1
这是一个AT个人刷题总结的开始,感觉确实应该做一做这种总结,如果只是不断的刷题,感觉貌似也没有什么意思,还不如时常适当的回望一下过去的好题。希望能一直做下去吧。
update(22.12.14): AT赛后总结归为另外一栏,此处为过去AT题目的记录。
总结了一些比较有趣或者有思维的题(对于我这个菜鸡)。
编号(NO.) | 题目 | 难度 | 类型 |
---|---|---|---|
1 | ARC060E | 2154 | 倍增 |
2 | ABC089D | 1220 | 思维,前缀和 |
3 | ABC087D | 1452 | 思维,DFS |
4 | ABC277D | 873 | 思维 |
5 | ABC277E | 1183 | 状态图,01BFS |
6 | ABC118D | 1657 | DP |
7 | ABC276E | 1058 | BFS,思维变形 |
8 | ABC078D | 1550 | 博弈论,思维 |
9 | ABC013C | 1692 | 思维 |
10 | ABC143E | 1695 | 思维,最短路 |
NO.1 ARC060E
倍增板题。
const int maxn=1e5+5;
int n,L,q;
int x[maxn],f[maxn][20],_2[20];
inline void Pre(){
for(int i=1;i<n;++i){
int l=i+1,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(x[mid]-x[i]>L) r=mid-1;
else l=mid;
} f[i][0]=l;
}
_2[0]=1;
for(int j=1;j<=18;++j){
_2[j]=(_2[j-1]<<1);
for(int i=1;i<=n;++i)
f[i][j]=f[f[i][j-1]][j-1];
}
}
inline void solve(int x,int y){
int res=0;
for(int i=18;i>=0;--i){
if(!f[x][i]||f[x][i]>y) continue;
x=f[x][i];
res+=_2[i];
} if(x<y) ++res;
cout<<res<<'\n';
}
int main(){
// freopen("E.in","r",stdin);
// freopen("E.out","w",stdout);
IOS
cin>>n;
for(int i=1;i<=n;++i)
cin>>x[i];
cin>>L>>q;
Pre();
int fo,to;
while(q--){
cin>>fo>>to;
if(fo>to) swap(fo,to);
solve(fo,to);
}
return 0;
}
NO.2 ABC089D
思维题,但是没想出来。其实很显然需要一个可以快速统计答案的方法。一开始上来想了下倍增,但是显然麻烦了。可以用一种前缀的思想去做,因为变量值为 \(d\) ,所以可以预处理出从最小值到某个数的步数,然后用前缀思想就好了。
const int maxn=1e5+5;
int n,m,d,q;
int f[maxn],x[maxn],y[maxn];
int main(){
// freopen("D.in","r",stdin);
// freopen("D.out","w",stdout);
IOS
cin>>n>>m>>d; int p;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
cin>>p,x[p]=i,y[p]=j;
for(int i=d+1;i<=n*m;++i)
f[i]=f[i-d]+fabs(x[i]-x[i-d])+fabs(y[i]-y[i-d]);
cin>>q;
int l,r;
while(q--){
cin>>l>>r;
cout<<f[r]-f[l]<<'\n';
}
return 0;
}
NO.3 ABC087D
这题应该很经典。
思路也比较好想到,对于一对关系,建边就好了,然后跑一边dfs,如果发现其中有一个关系不符合就直接返回false。可能有多个连通块所以要for一遍。
const int maxn=1e5+5;
const int maxm=2e5+5;
int n,m,cnt,head[maxn];
struct Edge{
int to,nxt,val;
}edge[maxm<<1];
inline void Add(int fo,int to,int val){
edge[++cnt]={to,head[fo],val};
head[fo]=cnt;
}
int dis[maxn];
bool vis[maxn];
inline bool dfs(int x,int d){
vis[x]=true,dis[x]=d;
for(int i=head[x];i;i=edge[i].nxt){
int &to=edge[i].to;
if(vis[to]){
if(dis[to]!=dis[x]+edge[i].val)
return false;
} else if(!dfs(to,d+edge[i].val))
return false;
}
return true;
}
inline void solve(){
for(int i=1;i<=n;++i)
if(!vis[i])
if(!dfs(i,0)){
cout<<"No\n";
return ;
}
cout<<"Yes\n";
}
int main(){
// freopen("D.in","r",stdin);
// freopen("D.out","w",stdout);
IOS
cin>>n>>m;
int fo,to,val;
for(int i=1;i<=m;++i){
cin>>fo>>to>>val;
Add(fo,to,val);
Add(to,fo,-val);
}
solve();
return 0;
}
NO.4 ABC277D
这题...比赛时候一上来就想了一个tarjan缩点,结果很显然,打的很麻烦...又臭又长
const int maxn=2e5+5;
const int maxm=1e6+5;
int n,mod,a[maxn],cnt,head[maxn];
map <int,int> mp;
struct Edge{
int fo,to,nxt;
}edge[maxm];
inline void Add(int fo,int to){
edge[++cnt]={fo,to,head[fo]};
head[fo]=cnt;
}
bool ins[maxn];
int dfn[maxn],low[maxn],s[maxn],top,belong[maxn],gp;
ll v[maxn];
inline void tj(int x){
dfn[x]=low[x]=++cnt,s[++top]=x,ins[x]=true;
for(int i=head[x];i;i=edge[i].nxt){
int &to=edge[i].to;
if(!dfn[to]){
tj(to);
low[x]=min(low[x],low[to]);
} else if(ins[to]) low[x]=min(low[x],low[to]);
}
if(dfn[x]==low[x]){
int c; ++gp;
do{
c=s[top--];
ins[c]=false;
belong[c]=gp;
v[gp]+=a[c];
} while(top&&c^x);
}
}
int in[maxn];
ll dis[maxn];
inline ll topo(){
queue<int>q; ll res=0;
for(int i=1;i<=gp;++i)
if(!in[i]) q.push(i),dis[i]=v[i];
while(!q.empty()){
int c=q.front(); q.pop();
for(int i=head[c];i;i=edge[i].nxt){
int &to=edge[i].to;
dis[to]=Max(dis[to],dis[c]+v[to]);
--in[to]; if(!in[to]) q.push(to);
}
}
for(int i=1;i<=gp;++i) res=Max(res,dis[i]);
return res;
}
ll tot;
inline void solve(){
for(int i=1;i<=n;++i){
if(!mp[a[i]])
mp[a[i]]=i;
}
for(int i=1;i<=n;++i){
if(a[i+1]==a[i])
Add(i,i+1);
if(mp[(1+a[i])%mod])
Add(i,mp[(a[i]+1)%mod]);
}
int m=cnt;
cnt=0;
for(int i=1;i<=n;++i)
if(!dfn[i]){
top=0;
tj(i);
}
cnt=0;
memset(head,0,sizeof(head));
for(int i=1;i<=m;++i){
int x=belong[edge[i].fo],y=belong[edge[i].to];
if(x^y){
Add(x,y);
in[y]+=1;
}
}
cout<<tot-topo()<<'\n';
}
int main(){
IOS
cin>>n>>mod;
for(int i=1;i<=n;++i)
cin>>a[i],tot+=a[i];
sort(a+1,a+1+n);
solve();
return 0;
}
跑了166ms。然而实际上,这题很好做。其实这道题麻烦(对我)主要还是在环那里,然而观察一下很容易(然而我没有)就能发现,其实循环的情况只可能是这样的形式:
\((m-2) \rightarrow (m-1) \rightarrow 0 \rightarrow 1...\)
所以很容易想到先排个序。那么因为给出的数都是小于 \(M\) 的,所以不用担心会有超出 \(M\) 的情况(草,忘了)。所以,开个两倍数组接在后面就完了啊!如果有循环,那就循环呗,最后再特判一下减出来会不会小于0就好了啊(因为如果整个都是个循环,可能会重复计算)!
const int maxn=2e5+5;
bool vis[maxn<<1];
int n,mod,a[maxn<<1];
ll tot;
inline void solve(){
for(int i=1;i<=n;++i)
a[i+n]=a[i];
int pos=1; ll res=a[1],ans=0;
a[n+n+1]=-1;
while(pos<=n+n+1){
if(a[pos+1]==a[pos]||a[pos+1]==(a[pos]+1)%mod)
res+=a[pos+1];
else
ans=Max(ans,res),res=a[pos+1];
++pos;
}
cout<<Max(0ll,tot-ans)<<'\n';
}
int main(){
// freopen("D.in","r",stdin);
// freopen("D.out","w",stdout);
IOS
cin>>n>>mod;
for(int i=1;i<=n;++i)
cin>>a[i],tot+=a[i];
sort(a+1,a+1+n);
solve();
return 0;
}
NO.5 ABC277E
嗯,做这个题时读错题了,虽然好像然并卵,因为我已经摆了。
实际上这个题挺巧妙的(虽然只评了个1200)。
考虑对于给定的图,他按了开关后,所有能走的边都走不脱了,反之。那么再按一次,他的状态又回来了,所以一个图他只有两个状态。容易想到把另一个状态的图建出来,那么对于一个点,如果他想要另一个状态,他就必须按开关才行,所以只有开关点才能连通两个状态作为桥梁。
那么接下来就很容易了,发现是个01BFS,那就B呗。
感觉这题思路还是挺巧妙的...
const int maxn=2e5+5;
int n,m,k,cnt=1,head[maxn<<1],a[maxn<<1];
struct Edge{
int fo,to,nxt,val;
}edge[maxn<<2];
inline void Add(int fo,int to,int val){
edge[++cnt]={fo,to,head[fo],val};
head[fo]=cnt;
}
deque <int> q;
int dis[maxn<<1];
bool vis[maxn<<1];
inline bool bfs_01(int pos){
memset(dis,0x3f,sizeof(dis));
q.push_front(pos);
dis[1]=0;
while(q.size()){
int x=q.front(); q.pop_front();
if(vis[x]) continue;
vis[x]=true;
for(int i=head[x];i;i=edge[i].nxt){
int &to=edge[i].to;
if(dis[to]>dis[x]+edge[i].val){
dis[to]=edge[i].val+dis[x];
if(edge[i].val) q.push_back(to);
else q.push_front(to);
}
}
}
return vis[n]||vis[n<<1];
}
bool yon[maxn<<1];
inline void solve(){
F(1,i,1,k){
Add(a[i],n+a[i],0);
Add(n+a[i],a[i],0);
}
if(bfs_01(1))
cout<<Min(dis[n],dis[n<<1])<<'\n';
else
cout<<-1<<'\n';
}
int main(){
// freopen("E.in","r",stdin);
// freopen("E.out","w",stdout);
IOS
cin>>n>>m>>k;
int fo,to,val;
for(int i=1;i<=m;++i){
cin>>fo>>to>>val;
if(val){
Add(fo,to,val);
Add(to,fo,val);
} else{
Add(fo+n,to+n,val^1);
Add(to+n,fo+n,val^1);
}
}
for(int i=1;i<=k;++i) cin>>a[i];
solve();
return 0;
}
NO.6 ABC118D
题意翻译:
有n根火柴,m种数字,数字1,2,3,4,5,6,7,8,9分别需要2,5,5,4,5,6,3,7,6根火柴,要求n根火柴全部都用完且拼成的数字最大,输出这个数字。
一道很好的dp思维题。
然而我又聪明地把它看成了一道贪心,然后扣了半天都没扣出个东西来...
实际上还是比较容易推出dp方程的,很显然,dp需要记录的应该是所能形成的最大数字,那么用string就好了,然后加个判断函数。
发现 \(n \leqslant 10^4\) ,那么dp思路就出来了:\(f_i\) 表示用了 \(i\) 根火柴所能组成的最大数字,显然,最后加上的数字最多就只有9个,那么暴力递推一下就好了。
当然,需要注意的是,它要求刚好用完 \(n\) 根火柴,所以还需要考虑初始化,所以我把 \(f_i\) 一开始都设为了 “-”,\(f_0\) 为空。
const int N=10010;
struct Node{int v,c;}a[10];
int n,m,mp[]={0,2,5,5,4,5,6,3,7,6};
string f[N];
inline bool check(string A,string B){
if(A=="-") return true;
if(A.length()<B.length()) return true;
else if(A.length()>B.length()) return false;
else return A<B;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin>>n>>m;
for(int i=1,x;i<=m;++i){
cin>>x;
a[i]={x,mp[x]};
}
for(int i=1;i<=n;++i)
f[i]="-";
f[0]="";
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j)
if(a[j].c<=i) if(f[i-a[j].c]!="-")
if(check(f[i],f[i-a[j].c]+char(a[j].v+'0')))
f[i]=f[i-a[j].c]+char(a[j].v+'0');
}
// for(int i=1;i<=n;++i)
// cout<<i<<':'<<f[i]<<'\n';
cout<<f[n]<<'\n';
return 0;
}
NO.7 ABC276E
是道很有趣但是又有点恼人的题...
初看就很简单,但是感觉好像也不是很好判断...
然后想了半天,一直在bfs和dfs之间纠结,最后发现了一种比较好维护的bfs方法。
因为其实如果能形成回路,那么一定存在一个一进一出,所以可以考虑先把出发点四周的四个临近的点标上不同的号,然后进行bfs,如果发现有两个不同的标号有相交,则说明形成了回路。
const int N=1000010;
int n,m,sx,sy,dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
char a[N];
inline int trans(int x,int y)
{return (x<1||x>n||y<1||y>m)?1e9:m*(x-1)+y;}
struct Node{int x,y,type;};
queue<Node> q;
inline bool check(){
for(int i=0;i<4;++i){
int x=trans(sx+dx[i],sy+dy[i]);
if(x>0&&x<=n*m&&a[x]!='#') q.push({sx+dx[i],sy+dy[i],i+1});
} a[trans(sx,sy)]='#';
while(!q.empty()){
Node c=q.front(); q.pop();
a[trans(c.x,c.y)]=c.type;
for(int i=0;i<4;++i){
int to=trans(c.x+dx[i],c.y+dy[i]);
if(to<1||to>n*m) continue;
if(a[to]!='#'&&a[to]!='.'&&a[to]!=c.type) return true;
if(a[to]=='.'){
q.push({c.x+dx[i],c.y+dy[i],c.type});
a[to]=c.type;
}
}
}
return false;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);
cin>>n>>m;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
cin>>a[trans(i,j)];
if(a[trans(i,j)]=='S')
sx=i,sy=j;
}
if(check()) cout<<"Yes\n";
else cout<<"No\n";
return 0;
}
NO.8 ABC078D
感觉很牛马的证明题。
首先,如果只有一张牌,那么显然答案是 \(abs(a_n-y)\)
如果有两张牌,那么答案就是 \(max(abs(a_n-a_{n-1}),abs(a_n-y))\)
现在看大于两张牌的情况,
首先,显然先手可以取 \(n-1\) 或者 \(n\) 张牌,使得最后的答案和 \(n=2\) 时的一样,那么取完后剩余排数大于1张时呢?结论就是,根本没有必要,所以实际上 \(n>2\) 的情况和 \(n=2\) 的情况是一模一样的。
为什么捏?思考一下,如果你把主动权交给了后手,那么此时还剩下的两张或者更多的牌中,显然后手可以取到最后只剩下一张牌,那么也就是说最后的答案可以由后手决定为 \(a_n-a_{n-1}\) ,然而实际上第一轮选择时先手就已经可以达到这个答案了,所以如果你先手不取 \(n-1\) 或者 \(n\) 张牌,那么就相当于给了后手更多的减小答案的可能性,其中后手可以选择先手就能决定的答案,或者选择更小的答案。即不可能有比先手直接完或者剩下一张更优的答案了。
所以先手还不如一开始就决定好,因此,\(n\geqslant2\) 时,\(ans=max(abs(a_n-a_{n-1}),abs(a_n-y))\)
感觉其实这道题挺神奇的,它根本就没有讨论中间拿取的过程,而直接得出了最后的结论,因为中间的过程根本就没有必要。
const int N=2005;
int n,a[N],fx,fy;
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);
cin>>n>>fx>>fy;
for(int i=1;i<=n;++i)
cin>>a[i];
if(n==1||a[n]==a[n-1]) return cout<<abs(a[n]-fy)<<'\n',0;
else cout<<max(abs(a[n]-a[n-1]),abs(a[n]-fy))<<'\n';
return 0;
}
NO.9 ABC013C
这题满分 \(101\) 确实很有意思...
首先思考,先吃一定比后吃优,所以不妨直接考虑吃多少天正常餐和简单餐。
假如吃 \(x\) 天正常餐,\(y\) 天简单餐,那么应该满足 \(h+bx+dy-e(n-x-y)>0\)
显然是个一次函数,所以对于每个 \(x \in [0,n]\) 都求出最小 \(y\) 即可。
signed main(){
//freopen();
//freopen();
IOS
//int T;
cin>>n>>h>>a>>b>>c>>d>>e;
ll ans=llinf;
for(ll x=0;x<=n;++x){
ll A=(e+b),B=(e+d),C=(h-e*n);
ll l=0,r=n-x;
while(l<r){
ll mid=(l+r)>>1;
if(A*x+B*mid+C>0) r=mid;
else l=mid+1;
} ans=Min(ans,x*a+l*c);
if(x*a+l*c==60) cout<<x<<' '<<l<<'\n';
}
cout<<ans<<'\n';
return 0;
}
NO.10 ABC143E
真是道好题。
首先看到 \(n \leqslant 300\) 就先跑一遍 \(Floyd\) ,注意有些边要删。然后就需要思考了。
不难想到只有间距小于 \(L\) 的两点才能一步到达,所以再建个新图,其中边连着原图中间距小于 \(L\) 的两点,且边权为 \(1\) 。然后再跑个最短路 \(bfs\) 就好了。
const int N=305;
int n,m,cnt,head[N];
struct Edge{int to,nxt;}edge[N*N];
inline void Add(int fo,int to){
edge[++cnt]={to,head[fo]};
head[fo]=cnt;
} ll l,dis[N][N];
void bfs(int ori){
queue<int> d;
d.push(ori);
dis[ori][ori]=0;
while(d.size()){
int pos=d.front(); d.pop();
for(int i=head[pos];i;i=edge[i].nxt){
int& to=edge[i].to;
if(dis[ori][to]>dis[ori][pos]+1){
dis[ori][to]=dis[ori][pos]+1;
d.push(to);
}
}
}
}
signed main(){
//freopen();
//freopen();
IOS
//int T;
cin>>n>>m>>l;
vector<pair<int,pair<int,ll>>> v;
for(int i=1;i<=m;++i){
int fo,to; ll val;
cin>>fo>>to>>val;
if(val>l) continue;
v.push_back(make_pair(fo,make_pair(to,val)));
v.push_back(make_pair(to,make_pair(fo,val)));
}
memset(dis,0x3f3f,sizeof(dis));
for(int i=1;i<=n;++i) dis[i][i]=0;
for(auto t:v) dis[t.first][t.second.first]=t.second.second;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
for(int k=1;k<=n;++k)
dis[j][k]=Min(dis[j][k],dis[j][i]+dis[i][k]);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(i^j&&dis[i][j]<=l)
Add(i,j);
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=n;++i) bfs(i);
int q;
cin>>q;
while(q--){
int s,t;
cin>>s>>t;
if(dis[s][t]>114514){
cout<<-1<<'\n';
continue;
} else cout<<dis[s][t]-1<<'\n';
}
return 0;
}
23.8.4 EOF