300iq Contest 1 简要题解
咕咕咕
A. Angle Beats
description
有一张(n imes m)的方阵,每个位置上标有*
,+
,.
中的一种。你要用尽可能多的(I)型和(L)型覆盖这个方阵(均占恰好(3)个位置),要求(I)型中间那个位置必须是+
,(L)型中间那个位置必须是*
或者+
,两者非中间的两个位置必须都是.
。
(1 le n, m le 100.)
solution
由于某种原因,这道题咕咕咕了。
B. Best Subsequence
description
给出(n,k)以及({w_1,w_2,...,w_n}),你需要找出一个长度为(k)的子序列(1 le i_1 < i_2 < ... < i_k le n),最小化(max{w_{i_1}+w_{i_2},w_{i_2}+w_{i_3},...,w_{i_{k-1}}+w_{i_k},w_{i_k}+w_{i_1}})。
(3 le k le n le 2 imes 10^5, 1 le w_i le 10^9.)
solution
假设答案是(mid),那么子序列中相邻(首尾也算相邻)两个数中至少有一个不超过(lfloorfrac{mid}{2} floor)。
二分(mid),找出所有不超过(lfloorfrac{mid}{2} floor)的数,显然这些树构成的子序列是合法的,接着相邻两个数中至多插入一个新数,线性扫一遍统计即可。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2e5+5;
int n,k,w[N],p[N];
int check(int mid){
int cnt=0,res=0;
for(int i=1;i<=n;++i)if(w[i]<=mid/2)p[++cnt]=i;
if(cnt<=1)return cnt;
for(int i=1;i<cnt;++i)
for(int j=p[i]+1;j<p[i+1];++j)
if(w[j]+max(w[p[i]],w[p[i+1]])<=mid){
++res;break;
}
for(int j=p[cnt]+1;j<=n;++j)
if(w[j]+max(w[p[cnt]],w[p[1]])<=mid)
return cnt+res+1;
for(int j=1;j<p[1];++j)
if(w[j]+max(w[p[cnt]],w[p[1]])<=mid)
return cnt+res+1;
return cnt+res;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)scanf("%d",&w[i]);
int l=2,r=2e9,res;
while(l<=r){
int mid=l+(r-l>>1);
if(check(mid)>=k)res=mid,r=mid-1;
else l=mid+1;
}
printf("%d
",res);return 0;
}
C. Cool Pairs
description
给出两个(n)阶排列(p,q)和一个参数(k),要求构造两个序列(a,b)满足:
- (-n le a_i, b_i le n)。
- (a_{p_1} le a_{p_2} le ... le a_{p_n}, b_{q_1} le b_{q_2} le ... le b_{q_n})。
- 满足(i<j, a_i<b_j)的((i,j))数量恰好为(k)。
(1 le n le 3 imes 10^5, 0 le k le frac{n(n-1)}{2}.)
solution
按照如下方法构造:
- (a_{p_i}=i-1-n)。
- 存在一个(xin[1,n]),使(forall iin[1,x), b_{q_i}=0, forall iin(x,n], b_{q_i}=n)。
可以发现这样构造出来的满足(i<j, a_i<b_j)的((i,j))数量为(sum_{i=1}^{x-1}(q_i-1)+y),其中(y)是一个与(b_{q_x})取值有关的在([0,q_x))范围内的数。易证明这种构造方法可以对任意合法的(k)构造出解。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=3e5+5;
int n,a[N],b[N],c[N];long long k;
int main(){
scanf("%d%lld",&n,&k);
for(int i=1,x;i<=n;++i)scanf("%d",&x),a[x]=i-1-n,b[i]=n;
for(int i=1,x;i<=n;++i){
scanf("%d",&x);
if(x-1<=k)b[x]=0,k-=x-1;
else{
for(int j=1;j<x;++j)c[j]=a[j];
sort(c+1,c+x);
b[x]=-c[k+1];break;
}
}
puts("Yes");
for(int i=1;i<=n;++i)printf("%d ",a[i]);puts("");
for(int i=1;i<=n;++i)printf("%d ",b[i]);puts("");
return 0;
}
D. Dates
description
在接下来的(t)天时间里,有(n)个女孩子想要和( ext{y}color{red}{ ext{yb}}) 约会。第(i)个女孩子想要在([l_i,r_i])中的某一天和( ext{y}color{red}{ ext{yb}})约会,且约会后( ext{y}color{red}{ ext{yb}})会得到(p_i)的偷税值。在这里,我们保证(l_ile l_{i+1}, r_i le r_{i+1})。( ext{y}color{red}{ ext{yb}})在第(i)天至多和(a_i)个女孩子约会,他希望你能帮他求出他能够获得的最大偷税值之和。
(1 le n, t le 3 imes 10^5, 0 le a_i le n, 1 le p_i le 10^9.)
solution
按照(p_i)从大到小依次加入区间,每次判断加入后是否仍存在完美匹配,这种做法的正确性显然。
根据(Hall)定理,判断是否存在完美匹配,只需要判断是否任意(1 le L le R le t)均满足(sum_{i=L}^Ra_i ge sum[l_i ge L][r_i le R])。
然后由于保证了(l_ile l_{i+1}, r_i le r_{i+1}),因而枚举(1 le L le R le t)可以替换为枚举(1 le L' le R' le n)即枚举“区间的区间”。设(b_iin{0,1})表示第(i)个区间是否被选,那么需要满足的限制就变成了:(sum_{i=L}^Rb_ile sum_{i=l_L}^{r_R}a_i)。
记(sa_i,sb_i)分别为(a_i)和(b_i)的前缀和,那么限制为(sb_R-sb_{L-1}le sa_{r_R}-sa_{l_L-1})即(sb_R-sa_{r_R}le sb_{L-1}-sa_{l_L-1})。再记(c_i=sb_i-sa_{r_i},d_i=sb_{i-1}-sa_{l_i-1}),限制为(c_Rle d_L)。
考虑每次加入一个区间即企图将某一个(b_x)由(0)改为(1),观察发现只会有(c_{x...n})与(d_{1...x})的大小关系会受到影响,因而维护后缀(c_i)的最大值和前缀(d_i)的最小值判断即可。
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
const int N=3e5+5;
struct segment_tree{
ll op,arr[N],val[N<<2],tag[N<<2];
ll merge(ll x,ll y){return op?max(x,y):min(x,y);}
void build(int x,int l,int r){
if(l==r){val[x]=arr[l];return;}
tag[x]=0;int mid=l+r>>1;
build(x<<1,l,mid);build(x<<1|1,mid+1,r);
val[x]=merge(val[x<<1],val[x<<1|1]);
}
void init(int n,ll _op){
op=_op;build(1,1,n);
}
void modify(int x,int l,int r,int ql,int qr,ll v){
if(l>=ql&&r<=qr){val[x]+=v;tag[x]+=v;return;}
int mid=l+r>>1;
if(ql<=mid)modify(x<<1,l,mid,ql,qr,v);
if(qr>mid)modify(x<<1|1,mid+1,r,ql,qr,v);
val[x]=merge(val[x<<1],val[x<<1|1])+tag[x];
}
ll query(int x,int l,int r,int ql,int qr){
if(l>=ql&&r<=qr)return val[x];
int mid=l+r>>1;
if(qr<=mid)return query(x<<1,l,mid,ql,qr)+tag[x];
if(ql>mid)return query(x<<1|1,mid+1,r,ql,qr)+tag[x];
return merge(query(x<<1,l,mid,ql,qr),query(x<<1|1,mid+1,r,ql,qr))+tag[x];
}
}T1,T2;
int n,m,p[N],id[N];ll sa[N],ans;
bool cmp(int x,int y){return p[x]>p[y];}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)scanf("%lld",&sa[i]),sa[i]+=sa[i-1];
for(int i=1,l,r;i<=n;++i){
scanf("%d%d%d",&l,&r,&p[i]);id[i]=i;
T1.arr[i]=-sa[r];T2.arr[i]=-sa[l-1];
}
T1.init(n,1);T2.init(n,0);sort(id+1,id+n+1,cmp);
for(int i=1;i<=n;++i)
if(T1.query(1,1,n,id[i],n)<T2.query(1,1,n,1,id[i])){
T1.modify(1,1,n,id[i],n,1);
if(id[i]<n)T2.modify(1,1,n,id[i]+1,n,1);
ans+=p[id[i]];
}
printf("%lld
",ans);return 0;
}
E. Expected Value
description
平面图上随机游走(每次等概率走一条出边),求从点(1)走到点(n)所需步数的期望模(998244353)。
(2 le n le 3000.)
solution
由于是平面图因此边数是(O(n))的。
学习zzq的候选队论文可以得到一个(O(nm))解稀疏矩阵的做法,其中(m)是矩阵非零元素数量。
但是由于这种做法较为复杂繁琐以致使人忍不住点击右上角,因为以下提交了一份每次选取非零位最少的列向量消元的做法,然后它过了。
有没有老鸽来证明(估计是不太行了)或证伪这玩意儿的复杂度鸭。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=3005;
const int mod=998244353;
int n,m,a[N][N],cnt[N],pos[N],ans[N];
void Inc(int &x,int y){x+=y;x>=mod?x-=mod:x;}
int fastpow(int x,int y){
int res=1;
while(y){if(y&1)res=1ll*res*x%mod;x=1ll*x*x%mod;y>>=1;}
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%*d%*d");
scanf("%d",&m);
for(int i=1,x,y;i<=m;++i){
scanf("%d%d",&x,&y);
Inc(a[x][x],mod-1);Inc(a[y][y],mod-1);
Inc(a[x][y],1);Inc(a[y][x],1);
Inc(a[x][n+1],1);Inc(a[y][n+1],1);
}
for(int i=1;i<=n+1;++i)a[n][i]=0;a[n][n]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=n+1;++j)
if(a[i][j])++cnt[i];
cnt[0]=1<<30;
for(int i=1;i<=n;++i){
int r=0;
for(int j=i;j<=n;++j)if(a[j][i]&&cnt[j]<cnt[r])r=j;
if(r!=i)swap(a[r],a[i]),swap(cnt[r],cnt[i]);
int inv=fastpow(a[i][i],mod-2),top=0;
for(int j=i;j<=n+1;++j){
a[i][j]=1ll*a[i][j]*inv%mod;
if(a[i][j])pos[++top]=j;
}
for(int j=i+1;j<=n;++j)
if(a[j][i]){
int t=mod-a[j][i];
for(int k=1;k<=top;++k){
if(a[j][pos[k]])--cnt[j];
a[j][pos[k]]=(a[j][pos[k]]+1ll*t*a[i][pos[k]])%mod;
if(a[j][pos[k]])++cnt[j];
}
}
}
for(int i=n;i;--i){
ans[i]=mod-a[i][n+1];
for(int j=i+1;j<=n;++j)
ans[i]=(ans[i]+1ll*(mod-ans[j])*a[i][j])%mod;
}
printf("%d
",ans[1]);return 0;
}
F. Free Edges
description
给一张(n)点(m)条边的图,初始时每条边都是白的,你需要选择若干条边染成黑色。
每当存在某一个点只有一条白色邻边时,这条白色邻边会变成黑色。
求出最少把多少条边染成黑色后可以使所有边都变成黑色。
$1 le n, m le 10^5. $
solution
其实就是问至少删掉多少条边后图中不存在环。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+5;
int n,m,fa[N],ans;
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)fa[i]=i;
for(int i=1,x,y;i<=m;++i){
scanf("%d%d",&x,&y);
if(find(x)!=find(y))fa[find(x)]=find(y);
else ++ans;
}
printf("%d
",ans);return 0;
}
G.Graph Counting
description
给定(n),求有多少张(2n)个点的图满足不存在完美匹配,且加入任意一条不在图中的边后存在完美匹配。两张图被认为不同当且仅当无法通过重标号使两张图完全一致。答案对(998244353)取模。
(1 le n le 5 imes 10^5.)
solution
翻题解抄了个结论:
一张图满足题述条件当且仅当存在若干点与其余所有点均有连边,设这些点的数量为(x),将这些点全部删去后,图中剩下的部分是(x+2)个大小为奇数的团。
那么在枚举了(x)后,答案等于将(2n-x)拆分成(x+2)个奇数的方案数,也等于将(n+1)拆分成(x+2)正整数的方案数。
对所有的(x)的答案求和,发现就是将(n+1)拆分成至少(2)个正整数的方案数,也就是(P(n+1)-1)。
所以只要求拆分数就行了。使用五边形数定理可以做到(O(nsqrt n))的复杂度,由于常数较小,可以通过(n le 5 imes 10^5)的测试数据。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5e5+5;
const int mod=998244353;
int n,m,f[N],g[N];
void Inc(int &x,int y){x+=y;x>=mod?x-=mod:x;}
int main(){
scanf("%d",&n);++n;
f[1]=1;f[2]=2;f[3]=5;f[m=4]=7;
while(f[m]<=n)++m,f[m]=3+2*f[m-2]-f[m-4];
for(int i=g[0]=1;i<=n;++i)
for(int j=1;f[j]<=i;++j)
if((j+1>>1)&1)Inc(g[i],g[i-f[j]]);
else Inc(g[i],mod-g[i-f[j]]);
printf("%d
",(g[n]+mod-1)%mod);
return 0;
}
H. Hall's Theorem
description
对于一张二分图,设(A)为二分图左侧的一个非空点集,定义(N(A))表示(A)集合中所有点的相邻点构成的集合的并,即,二分图右侧与(A)中至少一个点相邻的点的集合。
定义一个(A)是不好的当且仅当(|N(A)|<|A|)。
给出(n,k),要求构造一张左右各(n)个点的二分图,满足恰有(k)个左侧的非空点集是不好的。
(1 le n le 20, 0 le k < 2^n.)
solution
出于某种原因,以下均讨论好的点集的数量(我们认为非不好的点集就是好的点集)。
不妨将限制加强一些:构造一个数列({a_i}),要求(n ge a_1 ge a_2 ge ... ge a_n ge n),然后对于二分图左侧的第(i)个点,它向二分图右侧的前(a_i)个点连边。
枚举一个集合(A)中的最小元素(x),那么这个集合的(N(A))就等于(a_x),为保证集合是好的,我们要求集合的大小不能超过(N(A))也即(a_x),那么这种构造方式下好的点集的总量就是:$$sum_{i=1}^nsum_{j=0}^{a_i-1}inom{n-i}{j}$$
按照(i)从小到大的顺序贪心地构造(a_i)(这样甚至都不需要考虑(a_i)内部的偏序关系了),可以证明这种方法可以对任意合法的(k)构造出方案。
#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,c[23][23],m,a[666],b[666];
int main(){
scanf("%d%d",&n,&k);k=(1<<n)-1-k;
for(int i=c[0][0]=1;i<n;++i)
for(int j=c[i][0]=1;j<=i;++j)
c[i][j]=c[i-1][j]+c[i-1][j-1];
for(int i=n-1;~i;--i)
for(int j=0;j<=i;++j)
if(k>=c[i][j])k-=c[i][j],a[++m]=n-i,b[m]=j+1;
else break;
printf("%d
",m);
for(int i=1;i<=m;++i)printf("%d %d
",a[i],b[i]);
return 0;
}
I. Interesting Graph
给一张图,满足如下性质:
对于任意的大小为(7)的子集(A),都存在两点(a,bin A)以及一个点(c otin A),满足所有从(a)到(b)的路径均包含点(c)。
For any subset (A) of (7) vertices of the graph, there are some two vertices (a, b in A) and some vertex (c otin A) such that all paths from (a) to (b) contain vertex (c).
求对这张图进行(k(k=1,2,...,n))染色的方案数模(998244353)。
(1 le n, m le 10^5.)
solution
性质表明图中不存在大小大于等于(7)的连通块。
对所有连通块求出(dp_i)表示将该连通块划分成(i)个集合的方案数,这部分的复杂度不超过(O(n imes 3^6)),然后合并不同连通块时可以使用分治法法塔再套个多点求值发现(6)个点构成的本质不同的图只有一百多种,所以将(dp)数组相同的合并后直接暴力就好了。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=1e5+5;
const int mod=998244353;
int inv[10],C[10],n,m,mrk[N],tot,all,ban[100],dp[7][100],scc,baz[N];
vector<int>E[N],foo[N],bar[N];
inline void Inc(int &x,int y){x+=y;x>=mod?x-=mod:x;}
inline int fastpow(int x,int y){
int res=1;
while(y){if(y&1)res=1ll*res*x%mod;x=1ll*x*x%mod;y>>=1;}
return res;
}
void dfs(int u){
mrk[u]=tot++;
for(int v:E[u]){
if(!~mrk[v])dfs(v);
ban[1<<mrk[u]|1<<mrk[v]]=1;
}
}
int main(){
scanf("%d%d",&n,&m);inv[1]=1;
for(int i=2;i<10;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
for(int i=1,x,y;i<=m;++i)scanf("%d%d",&x,&y),E[x].push_back(y),E[y].push_back(x);
memset(mrk,-1,sizeof(mrk));
for(int u=1;u<=n;++u)
if(!~mrk[u]){
memset(ban,0,sizeof(ban));tot=0;
dfs(u);all=(1<<tot)-1;
for(int i=1;i<=all;i<<=1)
for(int j=0;j<=all;++j)
if(j&i)
ban[j]|=ban[j^i];
memset(dp,0,sizeof(dp));dp[0][0]=1;
for(int i=0;i<tot;++i)
for(int j=0;j<=all;++j)
if(dp[i][j])
for(int s=all^j,k=s;k;k=(k-1)&s)
if(!ban[k])
Inc(dp[i+1][j|k],dp[i][j]);
vector<int>vec(tot);
for(int i=0;i<tot;++i)vec[i]=dp[i+1][all];
foo[++scc]=vec;
}
sort(foo+1,foo+scc+1);tot=0;
for(int i=1;i<=scc;++i){
if(i==1||foo[i]!=bar[tot])
bar[++tot]=foo[i];
++baz[tot];
}
for(int i=1;i<=n;++i){
C[0]=i;
for(int j=1;j<6;++j)C[j]=1ll*C[j-1]*(i-j+mod)%mod*inv[j+1]%mod;
int res=1;
for(int j=1;j<=tot;++j){
int s=0;
for(int k=0;k<bar[j].size();++k)
Inc(s,1ll*bar[j][k]*C[k]%mod);
res=1ll*res*fastpow(s,baz[j])%mod;
}
printf("%d ",res);
}
puts("");return 0;
}
J. Jealous Split
description
有一个非负整数序列({a_i}),你要将他分成恰好(k)段,记(s_i)为第(i)段的和,(m_i)为第(i)段的最大值,你需要保证这种划分方案对任意(1 le i <k)满足(|s_i-s_{i+1}|le max(m_i,m_{i+1}))。
(3 le k le n le 10^5, 0 le a_i le 5 imes 10^4.)
solution
我们定义“对序列进行一次(val)分段”为(从前往后,从后往前)扫一遍序列,每当权值和大于等于(val)就分一段。二分出最大的(x)满足对序列进行(x)分段后权值和大于等于(x)的段数大于等于(k),令其为(mid),假设对序列进行(mid)分段后序列被恰好分成了(k)段,且所有段并起来为([1,n]),那么可以证明这种划分方案在原问题下是合法的。
若不满足,则可以对序列倒着做(mid+1)分段,通过某种方式(这里的证明咕咕咕了)可以证明正着做(mid)分段与倒着做(mid+1)分段存在至少一个公共划分点,且满足用前者划分的一段前缀拼上后者划分的一段后缀恰好可以得到原问题的一组合法解。
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
const int N=1e5+5;
int n,k,a[N],p1[N],t1,p2[N],t2;
int check(ll mid){
ll sum=0;int res=0;
for(int i=1;i<=n;++i){
sum+=a[i];
if(sum>=mid)++res,sum=0;
}
return res;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
ll l=0,r=5e9,res;
while(l<=r){
ll mid=l+r>>1;
if(check(mid)>=k)res=mid,l=mid+1;
else r=mid-1;
}
ll sum=0;
for(int i=1;i<=n;++i){
sum+=a[i];
if(sum>=res)p1[++t1]=i,sum=0;
}
sum=0;
for(int i=n;i;--i){
sum+=a[i];
if(sum>=res+1)p2[++t2]=i,sum=0;
}
puts("Yes");
if(t1==k&&p1[k]==n){
for(int i=1;i<k;++i)printf("%d ",p1[i]);
puts("");return 0;
}
for(int i=1;i<=t1;++i)
if(k-i>=1&&k-i<=t2&&p1[i]+1==p2[k-i]){
for(int j=1;j<=i;++j)printf("%d ",p1[j]);
for(int j=k-i-1;j;--j)printf("%d ",p2[j]-1);
puts("");return 0;
}
return 0;
}
K. Knowledge
description
有一个仅包含两种字符ab
的字符串(S),可对其进行的操作为在任意位置插入或删除子串aa
,bbb
以及ababab
。问字符串(S)在进行若干次操作后能够得到多少种不同的长度为(m)的串。
(1 le |S| le 3 imes 10^5, 1 le m le 10^9.)
solution
实际上任意一个串都可以经过若干次操作得到一个唯一的长度最短的串,而这个串的最长长度不会超过(4)。
通过大力枚举可以得知这个“唯一的长度最短的串”只有(12)种,因此矩乘计算答案即可,复杂度为(O(12^3log m))。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
const int mod=998244353;
map<string,string>trans;
map<string,int>state;
string str[15];int tot,n,m;
void add(string s){str[state[s]=++tot]=s;}
int f(string s){
string t;
for(int i=0;i<s.size();++i){
t+=s[i];
if(trans.count(t))t=trans[t];
}
return state[t];
}
struct mat{
int a[15][15];
mat(){memset(a,0,sizeof(a));}
int *operator [](int x){return a[x];}
mat operator * (mat b){
mat c;
for(int i=1;i<=tot;++i)
for(int j=1;j<=tot;++j)
for(int k=1;k<=tot;++k)
c[i][k]=(c[i][k]+1ll*a[i][j]*b[j][k])%mod;
return c;
}
}S,T;
int main(){
add("");
add("a");
add("b");
trans["aa"]="";
add("ab");
add("ba");
add("bb");
add("aba");
add("abb");
trans["baa"]="b";
add("bab");
add("bba");
trans["bbb"]="";
trans["abaa"]="ab";
trans["abab"]="bba";
trans["abba"]="bab";
trans["abbb"]="a";
trans["baba"]="abb";
add("babb");
trans["bbaa"]="bb";
add("bbab");
trans["babba"]="bbab";
trans["babbb"]="ba";
trans["bbaba"]="babb";
trans["bbabb"]="aba";
for(int i=1;i<=tot;++i){
S[i][i]=1;
++T[i][f(str[i]+"a")];
++T[i][f(str[i]+"b")];
}
cin>>n>>str[0]>>m;
while(m){if(m&1)S=S*T;T=T*T;m>>=1;}
cout<<S[f("")][f(str[0])]<<endl;
return 0;
}