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_{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;
}