AGC 044 部分简要题解
吹水
D 题因为输出了不合法询问 WA 了 10 发,罚时小能手石锤了。
A - Pay to Win
每次有四种方案,瞎写一个记忆化搜索就发现这题过了。
复杂度证明的话可以发现每次的状态都是 \(\lfloor \frac {n}{2^i3^j5^k}\rfloor\) 或者 \(\lceil \frac {n}{2^i3^j5^k} \rceil\) 的格式,于是就证明完了。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
map<ll,ll> ans;
ll A,B,C,D;
ll solve(ll x){
// cerr << x << endl;
if(!x)return 0;
if(ans.find(x)!=ans.end())return ans[x];
ll j=(x+2)/3*3,j2=x/3*3;
ans[x] = min((__int128)D*x,(__int128)5000000000000000000);
ans[x]=min(ans[x], solve(j/3)+D*abs(x-j)+B),ans[x]=min(ans[x], solve(j2/3)+D*abs(x-j2)+B*(j2>0));
j=(x+4)/5*5,j2=x/5*5;
ans[x]=min(ans[x],solve(j/5)+D*abs(x-j)+C),ans[x]=min(ans[x], solve(j2/5)+D*abs(x-j2)+C*(j2>0));
j=(x+1)/2*2,j2=x/2*2;
ans[x]=min(ans[x],solve(j/2)+D*abs(x-j)+A),ans[x]=min(ans[x], solve(j2/2)+D*abs(x-j2)+A*(j2>0));
// cerr << x << ":" << ans[x] << ":" << endl;
return ans[x];
}
int main()
{
int T;cin >> T;
while(T--){
ll n;cin >> n >> A >> B >> C >> D;
ans.clear();
printf("%lld\n",solve(n));
}
}
B - Joker
暴力更新最短路。因为 \(\sum d(i,j)\) 是 \(n^3\) 的,而每次访问一个节点更新的时候其最短路至少 \(-1\) ,故复杂度是 \(O(n^3)\) 。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 505;
int x[N*N], y[N*N];
int n;
int d[N][N];
bool del[N][N];
const int xx[4]={1,0,-1,0};
const int yy[4]={0,1,0,-1};
void extend(int x,int y){
// cout << x << " " << y << ":" << d[x][y] << endl;
for(int i=0;i<4;i++){
int nx=x+xx[i], ny=y+yy[i];
int nd=d[x][y]+1-del[nx][ny];
if(d[nx][ny]>nd){
d[nx][ny]=nd;
extend(nx,ny);
}
}
}
int main()
{
cin >> n;
for(int i=1;i<=n*n;i++){
int p;scanf("%d",&p);
x[i] = (p+n-1)/n,y[i] = p-(x[i]-1)*n;
// cout << x[i] << ":" << y[i] << endl;
}
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)d[i][j]=min(min(i,j),min(n-i+1,n-j+1));
int ans=0;
for(int i=1;i<=n*n;i++){
ans+=d[x[i]][y[i]]-1;
del[x[i]][y[i]]=1;
for(int j=0;j<4;j++){
int nx=x[i]+xx[j],ny=y[i]+yy[j];
d[x[i]][y[i]] = min(d[x[i]][y[i]], d[nx][ny]);
}
extend(x[i],y[i]);
}
cout << ans << endl;
}
C - Strange Dance
大概要求维护一个可以打标记的东西和全局取模下 \(+1\) 。
倒着维护一个 trie(根节点连出的边表示个位数),每次加可以暴力更新,以为只需要递归到新的 \(\text{ch[i][0]}\),加法是 \(O(\log n)\) 的。
最后 dfs 一下就行。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int M = 2e5+5;
char s[M];
struct Node{
int swp,ch[3],ed;
}t[2000010];
int cnt=1;
inline void ins(int sum){
int fir=1;
int s=sum;
for(int i=0;i<n;i++){
int q=sum%3;
sum/=3;
if(!t[fir].ch[q])t[fir].ch[q]=++cnt;
fir=t[fir].ch[q];
}
t[fir].ed=s;
}
void add(){
int fir=1;
for(int i=0;i<n;i++){
if(t[fir].swp){
swap(t[fir].ch[1],t[fir].ch[2]);
for(int j=0;j<3;j++)t[t[fir].ch[j]].swp^=1;
t[fir].swp=0;
}
int q=t[fir].ch[2];t[fir].ch[2]=t[fir].ch[1],t[fir].ch[1]=t[fir].ch[0];
t[fir].ch[0]=q;
fir=q;
}
}
int ans[2000010];
void dfs(int fir,int d,int quan,int s){
if(t[fir].swp){
swap(t[fir].ch[1],t[fir].ch[2]);
for(int j=0;j<3;j++)t[t[fir].ch[j]].swp^=1;
t[fir].swp=0;
}
if(d==n){
ans[t[fir].ed]=s;return;
}
for(int j=0;j<3;j++){
dfs(t[fir].ch[j],d+1,quan*3,s+quan*j);
}
}
int tot=0;
int main()
{
cin >> n;
tot=1;for(int i=1;i<=n;i++)tot*=3;
scanf("%s",s+1);
m=strlen(s+1);
for(int i=0;i<tot;i++){
ins(i);
}
for(int j=1;j<=m;j++){
if(s[j]=='S')t[1].swp^=1;
else add();
}
dfs(1,0,1,0);
for(int i=0;i<tot;i++)printf("%d ",ans[i]);puts("");
}
D - Guess the Password
首先考虑这样一件事情:
如果 \(s\) 是原串 \(T\) 的子序列,那么 \(s\) 的答案是 \(|T|-|S|\) ,不难发现这个东西是充要的。
那么我们显然可以先查询单个字符,选取最小的答案,此时这个答案对应的字符一定在 \(T\) 中出现过。于是我们得到了串长,显然,我们继续询问连续一段相同字符可以得到每个字符的出现次数。
那么现在考虑如何合并这样一个东西,现在有 \(S_1\) 和 \(S_2\) 两个串,保证无相同字符的时候,我们可以考虑将 \(S_1\) 依次插入到 \(S_2\) 中询问是否构成字串,这样的询问次数是 \(|S_1|+|S_2|\) 的。
每次合并最短的两个询问次数就变成 \(O(L\log L)\) 了。
代码:
#include<bits/stdc++.h>
using namespace std;
const int tot=26+26+10;
char enc(int t){
if(t<10)return '0'+t;
t-=10;
if(t<26)return 'a'+t;
t-=26;
return 'A'+t;
}
string s[tot];
int sum=tot;
int ttm=0;
int query(string s){
if(s.length()>128)return 0x3f3f3f3f;
++ttm;
if(ttm>=850)while(1);
cout << "?" << " " << s << endl;
fflush(stdout);
int ans=0;
cin >> ans;
return ans;
}
int len=0x3f3f3f3f;
int q[tot];
string merge(string a,string b){
int i=0,j=0;
int la=a.length(),lb=b.length();
string fir=string();
while(i<la&&j<lb){
string qr=fir;
qr+=a[i];
for(int k=j;k<lb;k++){
qr+=b[k];
}
if(query(qr)+qr.length()==len){
fir+=a[i++];
}else {
fir+=b[j++];
}
}
while(i<la)fir+=a[i++];
while(j<lb)fir+=b[j++];
return fir;
}
int main()
{
for(int i=0;i<tot;i++){
s[i]+=enc(i);
q[i]=query(s[i])+1;
len=min(len,q[i]);
}
for(int i=0;i<tot;i++){
if(q[i]==len){
while(query(s[i]+enc(i))+s[i].length()+1==len)s[i]+=enc(i);
}else s[i]=string();
}
while(1){
for(int i=0;i<tot;i++)for(int j=i+1;j<tot;j++)if(s[i].length()>s[j].length())swap(s[i],s[j]);
int t=0;
while(!s[t].length())++t;
if(t+1==tot)break ;
string nw=merge(s[t],s[t+1]);
s[t+1]=nw;
s[t]=string();
}
assert(query(s[tot-1])==0);
cout << "! "<< s[tot-1] << endl;
fflush(stdout);
}
E - Random Pawn
显然每个位置的决策一样。显然最大值是直接结束,所以可以将最大值变成两端,原题就成了 \(N+1(0\cdots N)\) 的序列上操作。
转移就是:
考虑消掉 \(B_i\),我们找一个 \(C_i\) ,那么:
现在就是要让:
这个方程很容易找一组解,之后我们令 \(F_i = E_i-C_i\), \(G_i = A_i - C_i\)。
那么转移就变成了
这玩意显然是个凸包,维护出来就行了。复杂度 \(O(n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
typedef long long ll;
int n;
ll tmp[N];
ll a[N], b[N], c[N], y[N];
inline long long crs(int x1,int x2,int x3){
return 1ll*(x2-x1)*(y[x3]-y[x1])-1ll*(y[x2]-y[x1])*(x3-x1);
}
int stk[N],top;
long double ans[N];
int main()
{
cin >> n;
for(int i=0;i<n;i++)scanf("%lld",&a[i]);
for(int i=0;i<n;i++)scanf("%lld",&b[i]);
int mx=0;
for(int i=1;i<n;i++)if(a[i]>=a[mx])mx=i;
for(int i=0;i<n;i++)tmp[i]=a[i];
for(int i=0;i<=n;i++)a[i]=tmp[(mx+i)%n];
for(int i=0;i<n;i++)tmp[i]=b[i];
for(int i=0;i<=n;i++)b[i]=tmp[(mx+i)%n];
// for(int i=0;i<=n;i++)cout << a[i] << ":" << b[i] << endl;
for(int i=2;i<=n;i++)c[i]=2*(c[i-1]+b[i-1])-c[i-2];
for(int i=0;i<=n;i++)y[i] = a[i]-c[i];
for(int i=0;i<=n;i++){
while(top>1&&crs(stk[top-1],stk[top],i)>=0)--top;
stk[++top]=i;
}
ll ans = 0;
for(int i=1;i<top;i++){
ll dx = stk[i+1]-stk[i];
ans += y[stk[i]]*(dx+1) + y[stk[i+1]]*(dx-1);
}
for(int i=0;i<n;i++){
ans+=2*c[i];
}
// cout << ans << endl;
double ret=ans;
ret/=2.00*n;
printf("%.12lf\n",ret);
}
F - Name-Preserving Clubs
咕咕咕