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)\) 的序列上操作。

转移就是:

\[E_i(1\le i< N) = \max(A_i,\frac{E_{i-1}+E_{i+1}}{2}+B_i) \]

考虑消掉 \(B_i\),我们找一个 \(C_i\) ,那么:

\[E_i-C_i(1\le i< N) = \max(A_i-C_i,\frac{E_{i-1}+E_{i+1}-C_{i-1}-C_{i+1}}{2}+\frac{C_{i-1}+C_{i+1}}{2}+B_i-C_i) \]

现在就是要让:

\[\frac{C_{i-1}+C_{i+1}}{2}+B_i-C_i=0 (1\le i < N) \]

这个方程很容易找一组解,之后我们令 \(F_i = E_i-C_i\)\(G_i = A_i - C_i\)

那么转移就变成了

\[F_i(1\le i< N) = \max(G_i,\frac{F_{i-1}+F_{i+1}}{2}) \]

这玩意显然是个凸包,维护出来就行了。复杂度 \(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

咕咕咕

posted @ 2020-05-25 22:10  jerome_wei  阅读(845)  评论(8编辑  收藏  举报