gym103371 XXII Open Cup, Grand Prix of Korea

A. Automatic Sprayer 2

只是需要简洁地解一下方程组。

将行和列的贡献分开考虑。令行的贡献为 \(r\),列的为 \(c\)

行和列的总贡献显然都是 \((e_{1,1}+e_{n,n})/2(n-1)\),都贡献了两次所有要除以 \(2\)

单独观察行,把第一行拿出来记作 \(a\),要求的记作 \(r\)

\[a_1=0\times r_1+1\times r_2+2\times r_3+\cdots+(n-1)\times r_n+c \]

\[a_2=1\times r_1+0\times r_2+1\times r_3+\cdots+(n-2)\times r_n+c \]

\[a_3=2\times r_1+1\times r_2+0\times r_3+\cdots+(n-3)\times r_n+c \]

发现 \(a_{i+1}-a_i=\sum_{j\le i}r_j+\sum_{j>i}r_j\),考虑加上或减去 \(r\) 的总贡献就可以得到其后缀/前缀和。

列同理,求出 \(r_i\)\(c_j\) 还要构造方案,按某种顺序遍历每个 \((i,j)\),则 \((i,j)\) 上的值为 \(\min(c_i,r_j)\),之后 \(c_i,r_j\) 再减去这个值即可。

正确性考虑建出最大匹配的二分图,这样贪心能保证最大流一直等于剩下的贡献之和。

#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1005;
int n;
ll r[N],c[N],a[N][N];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%lld",&a[i][j]);
	r[n]=c[n]=(a[1][1]+a[n][n])/(n-1)/2;
	for(int i=1;i<n;i++) r[i]=(a[i+1][1]-a[i][1]+r[n])/2,c[i]=(a[1][i+1]-a[1][i]+c[n])/2;
	for(int i=n-1;i;i--) r[i+1]-=r[i],c[i+1]-=c[i];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			ll x=min(r[i],c[j]);
			printf("%lld ",x);
			r[i]-=x;c[j]-=x;
		}
		puts("");
	}
	return 0;
}

B. Cilantro

首先有结论:两个 \(01\) 个数相等的序列 \(S\)\(T\)\(S\) 可以通过入栈和出栈操作转为 \(T\)

必要性显然。充分性:构造时让栈中只保留 \(01\) 中的一种。

不妨令 \(t_1=0\),若 \(s_i=s_j=0,i<j\)\(j\) 合法,则 \(i\) 一定合法。因为 \(i\) 入栈之后直接和 \(t_0\) 匹配一定不劣。

直接二分过不了。考虑增量判断是否合法,让先入栈的后出栈,和后面的匹配一定是最优的(若 \(s_i=1\) 也要尽量匹配)。

从前往后扫描 \(S\),从后往前扫描 \(T\)。若某个时刻 \(01\) 数量相等,则 \(S\)\(T\) 的下一个位置可以匹配,计数器清零。

答案就是所有能匹配位置中 \(s_i=0\) 的下标和。

#include <bits/stdc++.h>
using namespace std;
int main(){
	ios::sync_with_stdio(0);cin.tie(0);
	int n;
	string s,t;
	cin>>n>>s>>t;
	long long ans=0;
	for(int i=n-1,c=0,p=0;~i;i--)
		if(!c&&t[i]==s[p]) p++,ans+=p*(s[p-1]==t[0]);
		else c+=(t[i]=='Y'?1:-1)-(s[i+p]=='Y'?1:-1);
	printf("%lld\n",ans);
	return 0;
}

E. Goose Coins

感觉这个模型见过一万次了。

考虑一种使用最少货币的方案:从大到小,用 \(\lfloor\frac{p}{c_i}\rfloor\) 个第 \(i\) 个货币,之后 \(p\leftarrow p\bmod c_i\)

从这个方案进行调整,显然只能从大的调整到小的,不然没法整除。

从大到小 DP,设 \(f(i,j,k)\) 为转移完第 \(i\) 个货币,还可以用 \(j\) 个货币,从更大的额外调整过来的价值需要 \(k\) 个第 \(i\) 个货币才能抵消掉此时的 \(\min/\max\)

转移很简单,见代码。

复杂度 \(O(nk^2)\),不用滚动数组刚好开下。

#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=61,K=1001;
int n,k;
ll p,c[N],w[N],mi[N][K][K],mx[N][K][K];
inline void cmi(ll&x,ll y) {if(x>y) x=y;}
inline void cmx(ll&x,ll y) {if(x<y) x=y;}
int main(){
	scanf("%d%d%lld",&n,&k,&p);
	memset(mi,0x3f,sizeof mi);
	memset(mx,-0x3f,sizeof mx);
	for(int i=0;i<n;i++) scanf("%lld%lld",&c[i],&w[i]);
	mi[n][k][0]=mx[n][k][0]=0;
	for(int i=n-1;~i;i--){
		for(int j=0;j<=k;j++) for(int t=0;t<=j;t++){
			ll x=c[i+1]/c[i]*t+p/c[i];
			if(x<=j) cmi(mi[i][j][x],mi[i+1][j][t]),cmx(mx[i][j][x],mx[i+1][j][t]);
		}
		for(int j=k;j;j--) for(int t=j;t;t--)
			cmi(mi[i][j-1][t-1],mi[i][j][t]+w[i]),cmx(mx[i][j-1][t-1],mx[i][j][t]+w[i]);
		p%=c[i];
	}
	if(mx[0][0][0]<0) puts("-1");
	else printf("%lld %lld\n",mi[0][0][0],mx[0][0][0]);
	return 0;
}

F. Hedgehog Graph

神秘交互。很难想象出题人心理状态。过几天再详细揭秘。

I. Organizing Colored Sheets

若大小为 \((x,y)\) 的矩形非法,显然大小为 \((x+1,y)\)\((x,y+1)\) 的也非法。

大小为 \((x,y)\) 的矩形合法,当且仅当对于每个位置 \((i,j)\) 都有一个该大小的矩形能包含它。

此时 \(O(n^3)\) 找出所有极小的非法矩形是容易的,但是很难优化。

后文边界是包括 # 的。

如果只需要考虑所有边界上的点是否能被这个大小的矩形包含,那就有一个简洁的 \(O(n^2)\) 做法:

枚举边界上的点所在方向,现在假设左侧一定靠着边界,那么对于每一个 #.....#,枚举横着的长度,up 和 down 的前缀 \(\min\) 之和作为竖着的长度,那么这就是一个非法的矩形。还需要考虑横着长度为这一段长度加一,竖着的为 \(1\) 的情况。

但是这个结论很难一眼理解它的正确性。得证明一下。现在假设存在一个不靠近边界的位置非法,而靠近边界的一定合法:

考虑非法坐标组成的连通块,那么一定存在一个连通块不与边界相邻。

选择一个和这个连通块外相邻的点,和某个包含这个点的矩形,此时向这个连通块移动,一定没有合法的矩形。

那么移动后增加了一个条形,这个条形一定包含了 #,假设非法的位置为 X,合法的为 O,那么一定有形如 X...O# 的东西使 X 单独成为连通块。

但此时包含 O 的合法矩形一定也同时包含了 X,推出矛盾,证毕。

#include <bits/stdc++.h>
using namespace std;
const int N=3005;
int n,m,ans,b[N][N],_b[N][N],u[N][N],d[N][N];
char s[N][N],_s[N][N];
void solve(){
	swap(n,m);
	memcpy(_b,b,sizeof b);memcpy(_s,s,sizeof s);
	memset(u,0,sizeof u);memset(d,0,sizeof d);
	for(int i=0;i<=n;i++) for(int j=0;j<=m;j++)
		b[i][j]=_b[j][i],s[i][j]=_s[j][n-i+1];
	for(int j=1;j<=m;j++){
		for(int i=1;i<=n;i++) u[i][j]=(s[i][j]=='.')*(u[i-1][j]+1);
		for(int i=n;i;i--) d[i][j]=(s[i][j]=='.')*(d[i+1][j]+1);
	}
	for(int i=1;i<=n;i++) for(int j=1,l=0,su=N,sd=N;j<=m+1;j++)
		if(j>m||s[i][j]=='#'){
			if(j-l>1) b[1][j-l]=1;
			l=j;su=sd=N;
		}else b[(su=min(su,u[i][j]))+(sd=min(sd,d[i][j]))][j-l]=1;		
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
	for(int t=4;t--;) solve();
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) ans+=!(b[i][j]|=b[i-1][j]|b[i][j-1]);
	printf("%d\n",ans);
	return 0;
}
posted @ 2023-01-30 22:26  shrtcl  阅读(238)  评论(0编辑  收藏  举报