ABC227 题解

评分一览:21-30-692-1643-2377-2323-2207-3229

只做了 DEFGH。比赛链接

D. Project Planning

给定 \(N\) 个正数 \(A_i\),每次选择 \(K\) 个并将它们减去 \(1\),求出最多能做的操作步数。
\(1\le K\le N\le 2\times 10^5\)\(1\le A_i\le 10^{12}\)

Solution

考虑二分一个操作步数 \(s\),那么问题转化为了,能不能做 \(s\) 步上述操作。
结论:\(\ge s\) 的有 \(x\) 个,\(<s\) 的数和为 \(sum\),若 \(sum\ge (K-x)\times s\),那么可以做出,否则不可以。
时间复杂度 \(O(n\log \frac{\sum A_i}{K})\)

Code
const int N=2e5;
int n,K;
ll ans;
ll a[N+10],ma;
bool chk(ll x) {
	int cnt=0;ll sum=0;
	for(int i=1;i<=n;i++) {
		if(a[i]>=x) cnt++;
		if(a[i]<x) sum+=a[i];
	}
	// printf("%d %lld\n",cnt,sum);
	return sum>=1ll*(K-cnt)*x;
}
int main() {
	scanf("%d %d",&n,&K);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),ma+=a[i];
	ll L=1,R=ma/K;
	while(L<=R) {
		ll mid=(L+R)>>1;
		if(chk(mid)) L=mid+1;
		else R=mid-1;
	}
	printf("%lld\n",L-1);
}

E. Swap

给定一个字符串 \(S\),其字符集为 KEY,求交换相邻的两个字符至多 \(K\) 次之后,能够产生的字符串个数。
\(2\le |S|\le 30\)\(0\le K\le 10^9\)

Solution

\(f_{S,i}\) 表示目前的字符串是 \(S\),还有 \(i\) 次操作可以使用。
因为字符集很少,我们不难考虑枚举每一个字符,并将它提取到当前的最前面,他前面的字符则向后移一位。
不难发现相同的字符只需要枚举到第一个就退出,因为越前面花费的就越少,而且其答案没有本质变化。
使用记忆化搜索实现,时间复杂度约为 \(O(3^{|S|/3})\) 的样子。

Code
map<pair<string,int>,ll> memo;
ll solve(string S,int K) {
	int n=S.size();
	if(K<0) return 0;
	if(n<=1) return 1;
	auto p=make_pair(S,K);
	if(memo[p]!=0) return memo[p];
	ll ret=0;
	for(auto c:"KEY") for(int i=0;i<n;i++) if(S[i]==c) {
		ret+=solve(S.substr(0,i)+S.substr(i+1),K-i);break;
	}
	return memo[p]=ret;
}

F. Treasure Hunting

有一个 \(H\times W\) 的矩阵,现在从左上角走到右下角,对前 \(K\) 大的权值求和,输出最小的和。
\(1\le H,W\le 30\)\(1\le K<H+W\)

Solution

考虑枚举第 \(K+1\) 大的值 \(x\),设 \(f_{i,j,k}\) 表示现在走到了 \((i,j)\),已经有了 \(k\) 个大于 \(x\) 的数,他们的和是多少。
转移十分简单:当 \(A_{i,j}<x\)\(f_{i,j,k}=\min(f_{i-1,j,k}+f_{i,j-1,k})\),当 \(A_{i,j}>x\)\(f_{i,j,k}=\min(f_{i-1,j,k-1},f_{i,j-1,k-1})+A_{i,j}\),注意当 \(A_{i,j}=x\) 的时候需要跑两遍。
时间复杂度 \(O(H^2W^2K)\)

Code
const int N=30;
const ll inf=1e18;
int n,m,K;
int a[N+10][N+10];
ll f[N+10][N+10][N+N+10],ans=inf;
int main() {
	scanf("%d %d %d",&n,&m,&K);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) scanf("%d",&a[i][j]);
	for(int I=1;I<=n;I++)
		for(int J=1;J<=m;J++) {
			for(int i=0;i<=n;i++)
				for(int j=0;j<=m;j++)
					for(int k=0;k<=max(i+j-1,K);k++)
						f[i][j][k]=inf;
			if(a[I][J]>a[1][1]) f[1][1][0]=0;
			else if(a[I][J]<a[1][1]) f[1][1][1]=a[1][1];
			else f[1][1][0]=0,f[1][1][1]=a[1][1];
			// printf("%d:\n",a[I][J]);
			for(int i=1;i<=n;i++)
				for(int j=1;j<=m;j++)
					if(i!=1||j!=1) {
						if(a[I][J]>a[i][j]) for(int k=0;k<=i+j-1;k++) f[i][j][k]=min(f[i-1][j][k],f[i][j-1][k]);
						else if(a[I][J]<a[i][j]) for(int k=1;k<=i+j-1;k++) f[i][j][k]=min(f[i-1][j][k-1],f[i][j-1][k-1])+a[i][j];
						else {
							for(int k=0;k<=i+j-1;k++) f[i][j][k]=min(f[i-1][j][k],f[i][j-1][k]);
							for(int k=1;k<=i+j-1;k++) f[i][j][k]=min(min(f[i-1][j][k-1],f[i][j-1][k-1])+a[i][j],f[i][j][k]);
						}
						// for(int k=1;k<=i+j-1;k++) printf("f[%d][%d][%d] is %lld\n",i,j,k,f[i][j][k]);
					}
			ans=min(ans,f[n][m][K]);
		}
	printf("%lld\n",ans);
}

G. Divisors of Binomial Coefficient

\(\binom{N}{K}\) 的质因子个数。
\(1\le N\le 10^{12}\)\(0\le K\le \min(10^6,N)\)

Solution

考虑展开组合数,对上下分别求质因子个数。
不难发现大于 \(\sqrt{N}\) 的质因子可以在最后处理,那么只需要预处理 \(10^6\) 内的质数,然后对上下分别暴力分解就可以了。
时间复杂度 \(O(10^6\log K)\)

Code
using ll=long long;
#define int long long
const int N=1e6,mod=998244353;
bool pr[N+10];
ll n;int K,ans=1;
ll up[N+10],down[N+10];
signed main() {
	scanf("%lld %lld",&n,&K);
	memset(pr,1,sizeof pr);
	pr[0]=pr[1]=0;
	for(ll i=2;i*i<=N;i++) if(pr[i])
		for(ll j=i+i;j<=N;j+=i) pr[j]=0;
	for(ll i=1;i<=K;i++) down[i]=i;
	ll fuck=n-K+1;
	for(ll i=0;i<K;i++) up[i]=fuck+i;
	// for(int i=1;i<=K;i++) printf("%lld %lld\n",up[i],down[i]);
	for(int p=2;p<=N;p++) if(pr[p]) {
		int cnt=0;
		for(ll i=p;i<=K;i+=p) while(down[i]%p==0) down[i]/=p,cnt--;
		for(ll i=((n-K+1)+p-1)/p*p;i<=n;i+=p) while(up[i-fuck]%p==0) up[i-fuck]/=p,cnt++;
		// printf("%d %d\n",i,cnt);
		ans=ans*(cnt+1)%mod;
	}
	// printf("%d\n",ans);
	for(ll i=n-K+1;i<=n;i++) if(up[i-fuck]!=1) ans=1ll*ans*2%mod;
	printf("%lld\n",ans);
}

H. Eat Them All

有一个 \(3\times 3\) 的网格图,现在 Snuke 在 \((1,1)\),Snuke 重复如下操作:

  • 使当前位置的 \(A_{i,j}\) 减去 \(1\)
  • 走到与当前位置相邻的某一个格子。

试构造一组方案使得 Snuke 最终可以使得 \(A_{i,j}\) 全部归为 \(0\)
\(1\le A_{i,j}\le 100\)

Solution

不难发现对于每一个点,其周围边的经过次数之和是 \(2A_{i,j}\),可以以这个作为限制,列出一个十二元一次方程组,包括九个方程,瞎几把 yy 会发现有四个自由元。
这些全都可以暴力枚举,注意枚举的时候剪枝,最后要注意建出来的图能否连通。
然后将每一条边,设他被经过了 \(x_i\) 次,直接拆成 \(x_i\) 条边,不难发现这个图一定存在欧拉回路,直接大力跑就可以了。
时间复杂度 \(O(200^4)\)

Code
using pii=pair<int,int>;
#define se second
#define fi first
#define pb push_back
#define mp make_pair
int x[20],a[4][4];
bool used[200*200*200+10];
bool chk(int i) {
	if(x[i]<0) return 1;
	return 0; 
}
string ans;
char turn(int x,int y) {
	int xx=(x-1)/3,xy=(x-3*xx==0?3:x-3*xx);
	int yx=(y-1)/3,yy=(y-3*yx==0?3:y-3*yx);
	xx++,yx++;
	if(xx==yx&&xy+1==yy) return 'R';
	if(xx==yx&&xy-1==yy) return 'L';
	if(xy==yy&&xx+1==yx) return 'D';
	if(xy==yy&&xx-1==yx) return 'U';
	return '*';
}
struct UFS {
	int fa[10];
	void init() {for(int i=1;i<=9;i++) fa[i]=i;}
	int find(int x) {if(x==fa[x]) return x;return fa[x]=find(fa[x]);}
	void merge(int x,int y) {int fx=find(x),fy=find(y);if(fx==fy) return;fa[fx]=fy;}
} U;
int cnt=0;
vector<pii> G[10];
int st[10];
vector<int> path;
void connect(int u,int v,int I) {
	for(int i=1;i<=I;i++) G[u].pb(mp(v,i+cnt)),G[v].pb(mp(u,i+cnt));
	cnt+=I;
}
void dfs(int u) {
	// printf("visit %d\n",u);
	// for(auto v:G[u]) printf("%d %d\n",v.fi,v.se);
	for(int i=st[u];i<(int)G[u].size();i=max(i+1,st[u]))
		if(!used[G[u][i].se]) {
			used[G[u][i].se]=1;st[u]=i+1;
			dfs(G[u][i].fi);
		}
	path.pb(u);
}
signed main() {
	for(int i=1;i<=3;i++)
		for(int j=1;j<=3;j++) 
			scanf("%d",&a[i][j]);
	for(x[1]=0;x[1]<=200;x[1]++) {
		x[7]=2*a[1][1]-x[1];
		if(chk(7)) continue;
		for(x[2]=0;x[2]<=200;x[2]++) {
			x[8]=2*a[1][2]-x[1]-x[2];
			if(chk(8)) continue;
			x[9]=2*a[1][3]-x[2];
			if(chk(9)) continue;
			for(x[5]=0;x[5]<=200;x[5]++) {
				x[10]=2*a[3][1]-x[5];
				if(chk(10)) continue;
				x[3]=2*a[2][1]-x[7]-x[10];
				if(chk(3)) continue;
				for(x[6]=0;x[6]<=200;x[6]++) {
					x[11]=2*a[3][2]-x[5]-x[6];
					if(chk(11)) continue;
					x[12]=2*a[3][3]-x[6];
					if(chk(12)) continue;
					x[4]=2*a[2][3]-x[9]-x[12];
					if(chk(4)) continue;
					if(x[3]+x[4]+x[11]+x[8]!=2*a[2][2]) continue;
					U.init();
					if(x[1]) U.merge(1,2);
					if(x[2]) U.merge(2,3);
					if(x[3]) U.merge(4,5);
					if(x[4]) U.merge(5,6);
					if(x[5]) U.merge(7,8);
					if(x[6]) U.merge(8,9);
					if(x[7]) U.merge(1,4);
					if(x[8]) U.merge(2,5);
					if(x[9]) U.merge(3,6);
					if(x[10]) U.merge(4,7);
					if(x[11]) U.merge(5,8);
					if(x[12]) U.merge(6,9);
					bool fl=0;
					for(int i=1;i<=9;i++) {
						for(int j=1;j<=9;j++)
							if(U.find(i)!=U.find(j)) {
								fl=1;break;
							}
						if(fl) break;
					}
					if(fl) continue;
					connect(1,2,x[1]),connect(2,3,x[2]),connect(4,5,x[3]),connect(5,6,x[4]);
					connect(7,8,x[5]),connect(8,9,x[6]),connect(1,4,x[7]),connect(2,5,x[8]);
					connect(3,6,x[9]),connect(4,7,x[10]),connect(5,8,x[11]),connect(6,9,x[12]);
					// for(int i=1;i<=12;i++) printf("%d ",x[i]);puts("");
					// for(int i=1;i<=9;i++) {
					// 	// printf("%d:\n",i);
					// 	for(auto v:G[i]) printf("%d %d\n",i,v.fi);
					// }
					dfs(1),reverse(path.begin(),path.end());
					// for(auto i:path) printf("%d ",i);puts(""); 
					for(int i=1;i<(int)path.size();i++) ans+=turn(path[i-1],path[i]);
					cout<<ans<<endl;
					return 0;
				}
			}
		}
	}
	puts("NO");
}
posted @ 2021-11-15 12:06  cnyz  阅读(321)  评论(5编辑  收藏  举报