Atcoder AGC001
AGC001:
B:[AGC001B] Mysterious Light
题意:
给定一个光源,横着入射一个等边三角形,射出的光线形成新的三角形的边,在边上反射,求所有光线的长度。
分析:
我们根据给定样例的图,发现每个方向的长度其实是相同的。
而且画一些图,可以得出来一个结论:
每个方向边的总长 \(=n-gcd(n,x)\)
我们根据 \(gcd\) 的过程证明一下,还是比较容易理解的。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,x,ans;
signed main(){
cin>>n>>x;
ans=3*(n-__gcd(n,x));
cout<<ans<<endl;
system("pause");
return 0;
}
C: [AGC001C] Shorten Diameter
题意:
给定一个树,要求删除一些点使树的直径小于等于 \(k\) ,而且不影响连通性。
求删除的点最小值。
分析:
看数据范围,支持枚举每一个点。
考虑分析 \(k\) 奇偶性:
\(k\) 为偶数:
以 \(x\) 为根的树,向左右两边拓展 \(k/2\) 的长度,统计节点个数 \(ans\)。
\(k\) 为奇数
这种情况下,枚举点,左右两边的长度不同,因此考虑枚举边:
通过边的两点分别向下延申 \(k/2\) 的长度,统计左右两点节点个数加和 \(ans\)
最终需要删除的就是 \(n-ans\) 个节点。
代码:
#include<bits/stdc++.h>
const int N=1e5+5;
using namespace std;
int n,m;
int a[N],b[N],tot,num;
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++) scanf("%d",&a[i]);
if(m==1){
if(a[1]==1) printf("%d\n%d\n%d\n",1,1,1);
else printf("%d\n%d\n%d %d\n",a[1],2,1,a[1]-1);
}
else{
int cnt=0; for(int i=1;i<=m;i++) cnt+=a[i]%2;//最多出现两个奇数——奇数无法两边连边
if(cnt>2){puts("Impossible"); return 0;}
for(int i=1;i<=m;i++){
if(a[i]%2&&num==0){swap(a[1],a[i]);num++;}
else if(a[i]%2&&num==1){swap(a[m],a[i]);}
}
for(int i=1;i<=m;i++) printf("%d ",a[i]); puts("");
if(a[1]>1) b[1]=a[1]-1,tot=1;
for(int i=2;i<m;i++) b[++tot]=a[i]; b[++tot]=a[m]+1;
printf("%d\n",tot);
for(int i=1;i<=tot;i++) printf("%d ",b[i]); puts("");
}
system("pause");
return 0;
}
D: [AGC001D] Arrays and Palindrome
题意:
给定一个长度为 \(m\) 的序列 \(a\) ,\(\sum\limits_{i=1}^m a[i]=n\)。要求字符串 \(s\) 的区间: \([1-a_1],[a_1+1,a_1+a_2],[a_1+a_2+1,a_1+a_2+a_3]...\) 都为回文串。
要求求出来一个序列 \(b\),限制整个字符串必须只有一个字符。
分析:
这题简直是人类智慧。
一个回文串,我们可以连接左右两边相对应的字符,表示两个字符相同。
然后通过 \(a\) 序列就可以建出来一个图。
所有字符相同的情况就是: 整个图形成一个连通块。
然后通过一些神奇的思想(具体还是看洛谷的题解好了),我们可以通过 错位 来得到 \(b\) 数列。
注意整个图至少需要 \(n-1\) 条边才能互相连通,如果 \(a\) 形成的边数过少,也就是 \(a_i\) 是奇数的情况 \(>2\) ,绝对不可能形成连通图。
题解:
#include<bits/stdc++.h>
const int N=1e5+5;
using namespace std;
int n,m;
int a[N],b[N],tot,num;
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++) scanf("%d",&a[i]);
if(m==1){
if(a[1]==1) printf("%d\n%d\n%d\n",1,1,1);
else printf("%d\n%d\n%d %d\n",a[1],2,1,a[1]-1);
}
else{
int cnt=0; for(int i=1;i<=m;i++) cnt+=a[i]%2;//最多出现两个奇数——奇数无法两边连边
if(cnt>2){puts("Impossible"); return 0;}
for(int i=1;i<=m;i++){
if(a[i]%2&&num==0){swap(a[1],a[i]);num++;}
else if(a[i]%2&&num==1){swap(a[m],a[i]);}
}
for(int i=1;i<=m;i++) printf("%d ",a[i]); puts("");
if(a[1]>1) b[1]=a[1]-1,tot=1;
for(int i=2;i<m;i++) b[++tot]=a[i]; b[++tot]=a[m]+1;
printf("%d\n",tot);
for(int i=1;i<=tot;i++) printf("%d ",b[i]); puts("");
}
system("pause");
return 0;
}
F: [AGC001F] Wide Swap
题意:
给定排列 \(P\),当且仅当 \(i,j\) 满足 \(|p_i-p_j|=1\) 且 \(|i-j|\geq k\) 是可以交换 \(p_i\) 和 \(p_j\) ,求最终字典序最小的排列.
分析:
将下标和权值更换位置,整个序列就表示成:
\(q_{p_i}=i\),然后交换的标准替换为:\(|p_i-p_{i+1}|\geq k\)。
根据题意,\(p\) 字典序最小相当于要求 \(q\) 字典序最小。
也就是说 \(q_i=1\) 时 \(i\) 尽量小,然后 \(q_j=2\) 时 \(j\) 尽量小...
理解一下就是:\(q_i\) 大的尽量往后放,这样值小的就会在能达到的最前面。
然后,问题就转化成: 给出一个排列 \(r\) 为 \(q\) 的反序序列,要求最终排列的字典序最大。
其实,在排列中有一些数 \(|r_i,r_j|<k\),他们的相对顺序永远不会改变。
我们暴力地将 \((r_i,r_j)\) 连边,得到一张拓扑图,然后贪心跑以当前位置的数为关键字的拓扑序就好了。
但是,这样的连边会有很多无用的边,考虑优化:
从后往前遍历 \(r\), \(r_i\) 连向的数必然属于 \([r_i-k,r_i]\cup [r_i,r_i+k]\) .
发现这两个区间内所有的元素已经确定了相对顺序,所以我们只需要将 \(r_i\) 分别向两个区间内下标最小的元素连边就行了。可以用 线段树维护。
接着,利用 优先队列,排序时先处理 \(r_i\) 大的情况,然后得到一个字典序最大的拓扑序。
倒转后就是字典序最小的拓扑序,然后我们再把位置转化成权值赋值即可。
代码:
#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk make_pair
using namespace std;
const int N=5e5+5,inf=0x3f3f3f3f;
int n,k;
int p[N],pos[N];
int val[N<<2],deg[N],ans[N],m;
int nxt[N<<1],ver[N<<1],head[N],tot;
void add(int x,int y){
ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot; deg[y]++;
}
void modify(int x,int l,int r,int pos,int z){
val[x]=min(val[x],z);
if(l==r) return;
int mid=l+r>>1;
if(pos<=mid) modify(x<<1,l,mid,pos,z);
else modify(x<<1|1,mid+1,r,pos,z);
}
int query(int x,int l,int r,int L,int R){
if(L>R) return inf;
if(L<=l&&r<=R) return val[x];
int res=inf,mid=l+r>>1;
if(L<=mid) res=min(res,query(x<<1,l,mid,L,R));
if(R>mid) res=min(res,query(x<<1|1,mid+1,r,L,R));
return res;
}
priority_queue<int> q;
int main(){
memset(val,0x3f3f3f3f,sizeof(val));
cin>>n>>k;
for(int i=1,x;i<=n;i++){
scanf("%d",&x); p[x]=i;
}
reverse(p+1,p+n+1);
for(int x=n,y;x;x--){
y=query(1,1,n,max(p[x]-k+1,1),(p[x]-1)); if(y!=inf) add(p[x],p[y]);
y=query(1,1,n,(p[x]+1),min(p[x]+k-1,n)); if(y!=inf) add(p[x],p[y]);
modify(1,1,n,p[x],x);//位置为 p[x] 的情况下插入 x
}
for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
while(!q.empty()){
int x=q.top(); q.pop();
pos[++m]=x;//尽量字典序大的前面
for(int i=head[x];i;i=nxt[i]){
int y=ver[i]; if(--deg[y]==0) q.push(y);
}
}
reverse(pos+1,pos+n+1);
for(int i=1;i<=n;i++) ans[pos[i]]=i;//再回去
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
system("pause");
return 0;
}