AtCoder Regular Contest 197 (Div. 2)
被打爆了。
A - Union of Grid Paths
题目大意:有一个 \(H\times W\) 的网格图,给出从左上角 \((1,1)\) 走到右下角 \((H,W)\) 的一个路径串(包含 R、D、?),其中部分位置的字符未知,用 ? 表示。R 和 D 分别代表向右走和向下走。可以执行任意次操作,每次操作可以将 ? 替换成 R 或 D,要求替换后这个串是一个合法的从左上走到右下的操作序列,之后按照这个操作序列在网格图上走,并把所有经过的格子涂黑。问最终可以涂黑多少个格子。\(H,W\le 2\times 10^5\)
把所有 ? 优先替换成 R 做一次,再优先替换成 D 做一次,可以得到每一行可能被涂黑的最左边位置和最右边位置,之后就能计算答案了。
#include<bits/stdc++.h>
using namespace std;
#define N 200020
int T,n,m,l[N],r[N];
char s[N<<1];
void init()
{
scanf("%d%d",&n,&m);
scanf("%s",s+1);
for(int i=1;i<=n;i++)l[i]=m+1,r[i]=0;
int R=m-1,D=n-1;
for(int i=1;i<=n+m-2;i++)
if(s[i]=='R')R--;
else if(s[i]=='D')D--;
int x=1,y=1,t=R;
for(int i=1;i<=n+m-2;i++){
l[x]=min(l[x],y);
r[x]=max(r[x],y);
if(s[i]=='R')y++;
if(s[i]=='D')x++;
if(s[i]=='?'){
if(t)y++,t--;
else x++;
}
}
l[x]=min(l[x],y);
r[x]=max(r[x],y);
x=1,y=1,t=D;
for(int i=1;i<=n+m-2;i++){
l[x]=min(l[x],y);
r[x]=max(r[x],y);
if(s[i]=='R')y++;
if(s[i]=='D')x++;
if(s[i]=='?'){
if(t)x++,t--;
else y++;
}
}
long long ans=0;
for(int i=1;i<=n;i++)ans+=r[i]-l[i]+1;
printf("%lld\n",ans);
}
int main()
{
scanf("%d",&T);
while(T--)init();
}
赛时一直在爆 D,这题没来得及想,遗憾。
B - Greater Than Average
题目大意:给定数组,定义数组的权值为严格大于平均值的数字个数,求子序列中最大的权值。\(N\le 2\times 10^5\)
直接把赛时的思考路径放上来吧。
考虑最优子序列肯定是选一些能够产生贡献的数,然后再选一个权值的前缀把平均值拉下来
比如目前a[1],a[2],a[3]是不产生贡献的,那么考虑选了x个和为sum的数,那么就需要保证min of x>(sum+s[3])/(x+3)
那么相当于要选大于平均数的最小的x个数
于是考虑我们要让区间[l,r]的这些数产生贡献,就需要找到i满足
a[l]>(s[r]-s[l-1]+s[i])/(i+r-l+1)
(i+r-l+1)a[l]>s[r]-s[l-1]+s[i]
显然此时取i=l-1一定最优,因为可以尽量拉低平均值
所以就是考虑取一段前缀,然后求平均值确定分界线,统计答案
做一下补充说明。因为确定了取 \(i=l-1\) 一定最优,所以最优解中一定是取 \([1,r]\) 作为子序列。于是只需要枚举前缀,然后确定 \(l\) 的位置计算答案。
#include<bits/stdc++.h>
using namespace std;
#define N 200020
int T,n,a[N];
int main()
{
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+n+1);
long long sum=0;
int ans=0;
for(int i=1;i<=n;i++){
sum+=a[i];
ans=max(ans,i+1-(int)(upper_bound(a+1,a+i+1,sum/i)-a));
}
printf("%d\n",ans);
}
}
这题开场交了 3 发 CE,原因是没搞明白 (upper_bound(a+1,a+i+1,sum/i)-a) 的值的类型。
印象里 CF 也有一道类似的平均数相关的题,当年直接秒了,但是现在遇到这种题需要把思考过程写出来才能想明白,果然变菜了...
C - Removal of Multiples
题目大意:初始有一个集合 \(S\) 包含全体正整数,会执行 \(Q\) 次操作,每次操作给出数字 \(A,B\),将 \(S\) 中所有 \(A\) 的倍数移除,并询问第 \(B\) 小。\(Q\le 10^5,2\le A\le 10^9,B\le 10^5\)
注意到 \(B\) 和 \(Q\) 都是 \(10^5\) 级别的,所以考虑前 \(2\times 10^5\) 个质数,每次操作至多只会删除 \(1\) 个质数,所以答案不会超过第 \(2\times 10^5\) 个质数的大小。
所以把 \(M=3\times 10^6\) 以内的质数筛出来,并令 \(S=\{1,2,\dots,M\}\),这样足够我们进行操作。每次直接模拟对应操作并用树状数组维护前 \(i\) 个数里还有多少个数在集合中,询问时直接二分即可。虽然是双 \(\log\) 但是跑得飞快。
#include<bits/stdc++.h>
using namespace std;
#define N 3000010
int Q,a,b,t[N],M=3000000;
int cnt,p[N],v[N],g[N];
int lowbit(int x){return x&(-x);}
void cg(int x,int c){while(x<N)t[x]+=c,x+=lowbit(x);}
int ask(int x){int r=0;while(x)r+=t[x],x-=lowbit(x);return r;}
int get(int x)
{
int l=1,r=M,mid;
while(l<r){
mid=l+r>>1;
if(ask(mid)<x)l=mid+1;
else r=mid;
}
return l;
}
int main()
{
for(int i=2;i<N;i++){
if(!v[i])p[++cnt]=i;
for(int j=1;j<=cnt && i*p[j]<N;j++){
v[i*p[j]]=1;
if(i%p[j]==0)break;
}
}
for(int i=1;i<=M;i++)cg(i,1);
scanf("%d",&Q);
while(Q--){
scanf("%d%d",&a,&b);
if(a<=M && !g[a]){
for(int i=a;i<=M;i+=a)
if(!g[i])g[i]=1,cg(i,-1);
}
printf("%d\n",get(b));
}
}
水题,没啥好说的。
D - Ancestor Relation
题目大意:给定 \(N\times N\) 的 \(01\) 矩阵 \(A\),问有多少大小为 \(N\) 的以 \(1\) 为根的树满足 \(A_{i,j}=1\) 当且仅当 \(i,j\) 之间有祖先后代关系。\(N\le 400\)
考虑当 \(A_{i,j}=1\) 时谁是谁的祖先。设 \(b_i\) 为矩阵的第 \(i\) 行,可以用 bitset 存。那么若 \(i\) 是 \(j\) 的祖先则一定有 \(b_j\subseteq b_i\)。如果此时 \(b_i\neq b_j\) 则一定能判断出祖先后代关系,否则会有 \(b_i=b_j\)。
经过分析可以发现,若 \(i\) 是 \(j\) 的祖先且 \(b_i=b_j\),这意味着从 \(i\) 走到 \(j\) 的路径上一定是不存在分叉的,且路径上的所有点对应的 \(b\) 一定相同。而这些点相互之间可以任意调换位置,所以答案可以乘上 \(cnt!\)。
于是接下来只需要做合法性的判断,需要判断以下几点:
- \(b\) 相同的那些点一定要在 \(A\) 中连成一个完全子图
- \(A_{1,i}=A_{i,1}=1\) 必须成立
- 如果 \(b_i\) 和 \(b_j\) 没有相互包含关系,那么 \(A_{i,j}=0\),反之亦然
#include<bits/stdc++.h>
using namespace std;
#define N 410
#define MOD 998244353
int T,n,a[N][N],fa[N],fac[N];
bitset<N>b[N];
vector<int>d[N];
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
void Union(int x,int y){if(Find(x)!=Find(y))fa[Find(x)]=Find(y);}
void init()
{
long long ans=1;
scanf("%d",&n);
for(int i=1;i<=n;i++){
fa[i]=i;
d[i].clear();
b[i].reset();
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
if(a[i][j])b[i][j]=1;
}
for(int i=2;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(b[i]==b[j])Union(i,j);
for(int i=2;i<=n;i++){
if(!a[1][i])ans=0;
d[Find(i)].push_back(i);
}
for(int i=2;i<=n;i++)if(!d[i].empty()){
int sz=d[i].size();
for(int j=0;j<sz;j++)
for(int k=j+1;k<sz;k++){
int x=d[i][j],y=d[i][k];
if(!a[x][y])ans=0;
}
ans*=fac[sz];
ans%=MOD;
}
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
if((b[i]&b[j])==b[j] || (b[i]&b[j])==b[i]){
if(a[i][j]==0)ans=0;
}
else if(a[i][j])ans=0;
printf("%lld\n",ans);
}
int main()
{
fac[0]=1;
for(int i=1;i<N;i++)fac[i]=1ll*fac[i-1]*i%MOD;
scanf("%d",&T);
while(T--)init();
}
赛时犯蠢没有想到子集关系,一直在想 bitcount 的大小关系,于是就寄了,遗憾。
E - Four Square Tiles
题目大意:给定 \(N,H,W\),问有多少种在 \(H\times W\) 的网格图内放置 \(4\) 个 \(N\times N\) 方块的方案数,使得每个方块都在网格内,且每个格子最多只被覆盖一次。\(N,H,W\le 10^9;H,W\le 3N-1\)
由于 \(H,W\le 3N-1\),所以一行里最多存在两个不同的 \(N\times N\) 方块,对列也是一样。所以四个方块大致构成 左上、右上、左下、右下 的形状。于是可以先判掉 \(\min(H,W)\lt 2N\) 的情况。
基于上述结论,考虑怎么刻画 左上、右上、左下、右下 这四个块。可以发现 \((N,N),(N,2N),(2N,N),(2N,2N)\) 必须被四个不同的方块覆盖,于是以这四个格子作为四种块的代表元。
设这四个方块的左上角坐标分别为 \((a_1,b_1),(a_2,b_2),(a_3,b_3),(a_4,b_4)\),考虑这些变量之间的关系,可以列出:
- \(1\le a_1\),\(a_1+N\le a_3\),\(a_3+N-1\le H\)
- \(1\le a_2\),\(a_2+N\le a_4\),\(a_4+N-1\le H\)
- \(1\le b_1\),\(b_1+N\le b_2\),\(b_2+N-1\le W\)
- \(1\le b_3\),\(b_3+N\le b_4\),\(b_4+N-1\le W\)
发现这些不等式的正整数解组数非常好算(因为不同组之间相互独立),但这些还不够,因为我们还需要保证左上和右下不交,且右上和左下不交。
根据对称性,这两对方块相交的方案数显然相同。而且可以发现,这两对方块在上述四个不等式的约束下一定不会同时相交。所以我们只需要统计其中一种情况的方案数并减去他们就好,以左上右下相交为例,此时几个变量需满足条件(注意之前的四组条件也必须满足,这里一起写下来):
- \(1\le a_2\),\(a_2+N\le a_4\),\(a_4\le a_1+N-1\),\(a_1+N\le a_3\),\(a_3+N-1\le H\)
- \(1\le b_3\),\(b_3+N\le b_4\),\(b_4\le b_1+N-1\),\(b_1+N\le b_2\),\(b_2+N-1\le W\)
最后答案就是第一部分四组不等式解的个数减去两倍的第二部分两组不等式解的个数。
其中每一组不等式解的数量都是容易计算的,这里直接开搞。
- \(1\le a_1\),\(a_1+N\le a_3\),\(a_3+N-1\le H\),只需稍作变形就可化为 \(1\le a_1\lt a_3-N+1\le H-2N+2\),方案数就是 \(\binom{H-2N+2}{2}\)。
- \(1\le a_2\),\(a_2+N\le a_4\),\(a_4+N-1\le H\),同理可得方案数为 \(\binom{H-2N+2}{2}\)。
- \(1\le b_1\),\(b_1+N\le b_2\),\(b_2+N-1\le W\),同理可得方案数为 \(\binom{W-2N+2}{2}\)。
- \(1\le b_3\),\(b_3+N\le b_4\),\(b_4+N-1\le W\),同理可得方案数为 \(\binom{W-2N+2}{2}\)。
- \(1\le a_2\),\(a_2+N\le a_4\),\(a_4\le a_1+N-1\),\(a_1+N\le a_3\),\(a_3+N-1\le H\),变形后化为 \(1\le a_2\lt a_4-N+1\lt a_1+1\lt a_3-N+2\le H-2N+3\),方案数为 \(\binom{H-2N+3}{4}\)。
- \(1\le b_3\),\(b_3+N\le b_4\),\(b_4\le b_1+N-1\),\(b_1+N\le b_2\),\(b_2+N-1\le W\),同理可得方案数为 \(\binom{W-2N+3}{4}\)。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MOD 998244353
const LL inv2=499122177;
const LL inv24=291154603;
LL C(LL n,LL m)
{
if(m==2)return n*(n-1)%MOD*inv2%MOD;
return n*(n-1)%MOD*(n-2)%MOD*(n-3)%MOD*inv24%MOD;
}
int main()
{
int T,n,h,w;
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&h,&w);
if(min(h,w)<2*n){
printf("0\n");
continue;
}
LL ans=C(h-2*n+2,2)*C(h-2*n+2,2)%MOD;
ans*=(C(w-2*n+2,2)*C(w-2*n+2,2)%MOD),ans%=MOD;
LL d=2ll*C(h-2*n+3,4)*C(w-2*n+3,4)%MOD;
printf("%lld\n",(ans+MOD-d)%MOD);
}
}
感觉是很好的基础容斥计数题。

浙公网安备 33010602011771号