我现在板刷 AGC 有两个目的。一个是为了多刷点题增加点练习量,多见点套路和题型。另一个是模拟赛搬AGC的时候不至于保龄。
然后今天模拟赛三个套路题切爽了(虽然挂了不少分)。所以板刷一套 AGC 来打击打击心态。
[AGC007A] Shik and Stone
普及。珍惜现在 A 题还是普及的时候吧。
char s[10][10];
int n,m,cnt;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i][j]=='#')cnt++;
}
}
if(cnt==n+m-1)puts("Possible");
else puts("Impossible");
return 0;
}
[AGC007B] Construct Sequences
一个显然的构造是先把 \(a\) 变成 \(1-n\),\(b\) 变成 \(n-1\)。然后顺序扫 \(p\),对应位置加上。
然后这样会不满足 \(a,b\) 的单调性。发现我们只需要加不超过 \(n\) 的数,那一开始随便乘个比 \(n\) 大的数就行了。
int n,p[20010],a[20010],b[20010];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&p[i]);
for(int i=1;i<=n;i++)a[i]=(n+1)*i,b[i]=(n+1)*(n-i+1);
for(int i=2;i<=n;i++){
if(a[p[i-1]]+b[p[i-1]]>=a[p[i]]+b[p[i]])b[p[i]]+=i;
}
for(int i=1;i<=n;i++)printf("%d ",a[i]);printf("\n");
for(int i=1;i<=n;i++)printf("%d ",b[i]);printf("\n");
return 0;
}
[AGC007C] Pushing Balls
神秘腧穴题。终于到了 C 题就开始不会的时候了。
打表可以得到每次滚一个球之后的距离序列的期望仍然是等差数列。然后考虑计算。
有 \(2n\) 种滚球方式,对 \(d_i\) 产生影响的操作有两种:
- 滚完前 \(i\) 段,此时原来的 \(d_{i+1}\) 无了,\(d_{i+2}\) 跑到了 \(d_i\) 的位置。
- 滚了第 \(i+1\) 段,此时原来 \(d_i,d_{i+2},d_{i+3}\) 并起来变成了 \(d_i\) 。
那么简单推导可以得到新的 \(d_i\) 的期望是: \(d_i+\dfrac{i\times 2x+2d+5x}{2n}\) 。回带 \(x\) 可以得到新的 \(x\) 为 \(x+\dfrac {2x}n\)。每次推球的期望距离是所有的平均值,在等差数列里也就是中位数。
int n;
double d,x,ans;
int main(){
scanf("%d%lf%lf",&n,&d,&x);
for(int i=n;i>=1;i--){
ans+=d+1.0*(i*2-1)/2*x;
d=d+(2*d+5*x)/(2*i);
x=x+2*x/i;
}
printf("%.12lf\n",ans);
return 0;
}
[AGC007D] Shik and Game
水题。
一眼 dp。然后每次走肯定是往前走一段再走回来收钱。考虑设 \(dp_i\) 为收完前 \(i\) 个时来回走花的时间,答案就是 \(dp_n+E\) 。
那么转移考虑从前面一个小熊的位置走过来在走回去,是 \(dp_i=dp_j+\max(2(a_i-a_{j+1}),T)\) 。
分两种情况讨论。\(\max\) 的第一项可以发现是单调的,整个单调指针扫一遍就行了。然后指针最多扫到 \(T\) ,那么整个单调队列随便维护一下就行了。
最近写单调队列老是忘记进队。
#define int long long
int n,E,T,a[100010],q[100010],l,r,dp[100010];
signed main(){
memset(dp,0x3f,sizeof(dp));
scanf("%lld%lld%lld",&n,&E,&T);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
l=r=1;int mn=0x3f3f3f3f;
dp[0]=0;
for(int i=1;i<=n;i++){
while(l<=r&&2*(a[i]-a[q[l]+1])>=T){
mn=min(mn,dp[q[l]]-2*a[q[l]+1]);
l++;
}
dp[i]=min(dp[i],dp[q[l]]+T);
dp[i]=min(dp[i],mn+2*a[i]);
q[++r]=i;
}
printf("%lld\n",dp[n]+E);
return 0;
}
[AGC007E] Shik and Travel
51nod 模拟赛搬过的题,当时完全不会。现在也只会某个 \(O(n^2\log n)\) 的东西。
最大值最小,二分答案。然后考虑到这个 dp 应该从左右儿子里各选一条边进来出去。
设 \(dp_{x,i,j}\) 为 \(x\) 为根的子树内,从根节点到叶子的距离为 \(i\) ,叶子到根的距离为 \(j\) ,所有叶子到叶子的路径都小于当前二分的值 \(mid\) 的方案是否存在。那么转移考虑枚举两个叶子拼接起来,复杂度是 \(O(n^2)\) 的。
发现当两个状态 \(dp_{x,i_1,j_1},dp_{x,i_2,j_2}\) 满足 \(i_1\le i_2\) 且 \(j_1\le j_2\) 时,后者是完全没必要存在的。于是我们现在将状态压缩到了 \(2n\) 级别的。使用启发式合并类似的复杂度证明可以得到总状态数是 \(O(n\log n)\) 的。
实现其实了解了思路之后比较好写,每个节点开个 vector 存状态,枚举左右儿子一个进去一个出去,双指针扫一遍即可。
#define int long long
int n,t,son[150010][2],w[150010][2];
vector<pair<int,int> >v[150010];
void dfs(int x,int val){
v[x].clear();
if(!son[x][0]){
v[x].push_back(make_pair(0,0));return;
}
dfs(son[x][0],val);dfs(son[x][1],val);
vector<pair<int,int> >ret;
for(int s=0;s<2;s++){
int tmp=val-w[x][0]-w[x][1];
for(int i=0,j=0;i<v[son[x][s]].size();i++){
while(j+1<v[son[x][s^1]].size()&&v[son[x][s^1]][j+1].first<=tmp-v[son[x][s]][i].second)j++;
if(j>=v[son[x][s^1]].size()||v[son[x][s^1]][j].first>tmp-v[son[x][s]][i].second)continue;
ret.push_back(make_pair(v[son[x][s]][i].first+w[x][s],v[son[x][s^1]][j].second+w[x][s^1]));
}
}
sort(ret.begin(),ret.end());
for(pair<int,int>p:ret){
if(!v[x].empty()&&v[x].back().second<=p.second)continue;
v[x].push_back(p);
}
}
signed main(){
scanf("%lld",&n);
int l=0,r=0;
for(int i=2;i<=n;i++){
int f,val;scanf("%lld%lld",&f,&val);
r+=val;
if(son[f][0])son[f][1]=i,w[f][1]=val;
else son[f][0]=i,w[f][0]=val;
}
while(l<r){
int mid=(l+r)>>1;
dfs(1,mid);
if(!v[1].empty())r=mid;
else l=mid+1;
}
printf("%lld\n",l);
return 0;
}
[AGC007F] Shik and Copying String
神必贪心。——cmd
首先每个连续段显然由最右边能转移的转移过来。于是我们可以将所有变化看成折线,折线最靠右就行。
考虑怎么维护这个东西。划拉半天终于看懂了。
如图。
开一个队列记录向右拐的拐点的位置。然后每次的操作相当于把整条折线向左下移动一格(可以手模一下)。我们找到折线的起点和终点,然后把所有在终点后面的拐点扔掉,最后如果折线不是从上到下一条直线的话就把拐点加入进来,统计答案(就是拐点数量的最大值)。
记得特判相等。
int n,l,r,ans,q[1000010];
char s[1000010],t[1000010];
int main(){
scanf("%d%s%s",&n,s+1,t+1);l=1;
if(!strcmp(s+1,t+1)){
puts("0");return 0;
}
for(int i=n,pos=n;i>=1;i--){//i是终点 pos是起点
if(t[i]!=t[i-1]){
pos=min(pos,i);
while(pos&&s[pos]!=t[i])pos--;
if(!pos){
puts("-1");return 0;
}
while(l<=r&&q[l]-(r-l)>i)l++;//把在i后面的扔掉
if(i!=pos)q[++r]=pos;
ans=max(ans,r-l+1);
}
}
printf("%d\n",ans+1);
return 0;
}
完了。
标签:AGC007,int,pos,son,ans,return,dp From: https://www.cnblogs.com/gtm1514/p/16897422.html