2023 OI 集训营提高组第五场题解

T1 矩阵交换

  • 题目提到行排序后看每列内部的关系。因此自然的分析列内的性质。

  • 如果则直接输出YES容易解决,考虑

  • 考虑以第列元素为关键字对行进行排序,形成了的序列。认为是三个区间,其中

alt

。此时考虑第列元素,,即第列形成的个区间在第列内要满足非递减关系。

  • 总结下是:前一列形成了若干个区间在当前列满足非递减关系。

  • 采用上述方法从第列推到第列,该问题解决。

T2 砖块摆放

把三种颜色分别当作 0、1、2 三种数字、

假设相邻两个砖块的颜色为 ,那么通过大眼观察法可以发现上面的一个它们上面的砖块等于 意义下

根据公式可以发现砖块中每个数字和二项式定理(杨辉三角系数)的形式一致,即可用组合数学获得答案。

T3 学习 lis

10分:枚举然后check即可,check的过程稍微加速一下,一旦不满足就返回,否则会TLE。

20分:我们显然可以一边dfs一边check前面生成的序列是否合法,这样的数据就卡不掉了,复杂度接近答案的大小。

30分:其实我们并不关心是多少,只关心我们生成的序列,一共出现了多少种不同的数字,以及这些数字必须得是从开始连续的,比如,我们可以用来代表所有类似于类的序列,最后乘上组合数即可,所以在的过程中,记录一共用了多少种数字,以及哪些数字被用到了,稍微剪枝一下就可以了。

另外40分:考虑一个状压,我们每次填入相同数字到一个子集去,比如这个方案,我们可以先给位置填入,变成,然后再给第个位置填入数字变成,这个过程中,凡是填入的数字的LIS都是可以算出来的。

状态是:表示我考虑完了数字,填入了这个集合的方案数。

转移不表,复杂度,但是由于给定LIS数组的限制,严重卡不满。

#include <bits/stdc++.h>
#define ll long long
#define maxn 1000005
#define mod 998244353 
using namespace std;
ll C[3005][3005];
void init()
{
	C[0][0]=1;
	for (int i=1;i<=3000;i++)
		for (int j=0;j<=i;j++)
		{
			if (i==1 || j==0) C[i][j]=1;
			else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
		}
}
int n,m;
int a[21];
ll dp[1<<20][21]; //填了i种数字,填了哪些位置的方案数 
void add(ll &x,ll y) {x=(x+y)%mod; return;}
int can(int sta,int nxt)
{
	int mx=0;
	for (int i=0;i<n;i++)
	{
		if ((sta>>i)&1) mx=max(mx,a[i]);
		if ((nxt>>i)&1) if (a[i]!=mx+1) return 0;
	}
	return 1;
}
int main()
{
	init();
	cin>>n>>m;
	for (int i=0;i<n;i++) cin>>a[i];
	dp[0][0]=1;
	for (int sta=0;sta<(1<<n);sta++)
	for (int i=0;i<n;i++)
	if (dp[sta][i])
	{
		int bu=(1<<n)-1-sta;
		for (int S=bu;S!=0;S=(S-1)&bu)	
		if (can(sta,S))
		{
			add(dp[sta|S][i+1],dp[sta][i]);			
		}
	}
	ll ans=0;
	for (int i=1;i<=n;i++) add(ans,dp[(1<<n)-1][i]*C[m][i]);
	cout<<ans<<endl;
}

100分:考虑把状压转移子集的过程拎出来:表示的是,我正在考虑数字的填写,当前在从后向前考虑第个位置填不填数字,当前状态是的方案数。

转移不表,有一个小细节是,我们并不知道有没有用到,这样你可以选择加一个状态来表示是否用过了,也可以选择就这么下来,这样你得到的答案表示的是,最多用了个不同数字,构造出的序列满足LIS数组的方案数。

容斥一下:alt

即是恰好使用了个数字的方案数了。

时间复杂度,需要滚动一下数组。

#include <bits/stdc++.h>
#define ll long long
#define maxn 3000005
#define mod 998244353 
using namespace std;
ll C[3005][3005];
void init()
{
	C[0][0]=1;
	for (int i=1;i<=3000;i++)
		for (int j=0;j<=i;j++)
		{
			if (i==1 || j==0) C[i][j]=1;
			else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
		}
}
int n,m;
int a[21];
int premax[1<<20];
void init2()
{
	for (int sta=0;sta<(1<<n);sta++)
	{
		for (int i=0;i<n;i++)
		if ((sta>>i)&1)
			premax[sta]=max(premax[sta],a[i]);
	}
}
int dp[2][22][1<<20];
ll ans[25]; 
void add(int &x,int y) {x=(x+y)%mod; return;}
void DP()
{
	int now=0,nxt=1;
	dp[now][n-1][0]=1;
	for (int turn=1;turn<=n;turn++)
	{
		for (int i=n-1;i>=0;i--)
		for (int sta=0;sta<(1<<n);sta++)
		if (dp[now][i][sta])
		{
			int tmp=dp[now][i][sta];
			//case1:不选 
			if (i) add(dp[now][i-1][sta],tmp);
			else add(dp[nxt][n-1][sta],tmp);
			//case2:选
			if (((sta>>i)&1)==0 && premax[sta&((1<<i)-1)]+1==a[i])
			{
				if (i) add(dp[now][i-1][sta|(1<<i)],tmp);
				else add(dp[nxt][n-1][sta|(1<<i)],tmp);
			}
			dp[now][i][sta]=0;
		}
		//此时dp[nxt][n-1][sta]就是选了最多turn个数的时候的方案数
		ans[turn]=dp[nxt][n-1][(1<<n)-1];
		swap(now,nxt);
	}
	ll tot=0;
	for (int i=1;i<=n;i++)
	{
		for (int j=i-1;j>=1;j--)
		{
			ans[i]=(ans[i]+(-1*C[i][j]*ans[j]%mod)+mod)%mod;
		}
		tot=(tot+ans[i]*C[m][i]%mod)%mod;
	}
	cout<<tot<<endl;
}
int main()
{
	cin>>n>>m;
	for (int i=0;i<n;i++) cin>>a[i];
	init(); init2();
	DP();
}

T4 战略轰炸

首先断环为链。

下文中 表示联通之后的军事基地的战斗力的最大值。

考虑给定 的轰炸机是否可以完美轰炸 次的本质是判断 。 原因是因为一共轰炸 次,一个军事基地最多被轰炸 次,而若 ,则代表每次都可以选择轰炸它。否则对剩余的 的军事基地轮流轰炸,即求和之后除以 下取整,最后加起来判断是否能否有大于等于 个军事基地被一同选中。

  • 特殊性质

即每次询问一个 ,求 。 考虑对 数组排序,设定一个阈值 ,当 时,预处理计算。当 时,枚举 用树状数组统计。再特判一下 的数的个数即可。取 ,则复杂度为

  • 特殊性质

即给定一个长度为 的数组 次操作。

  • 表示将

  • 表示求

考虑对于 的每个 建立一个树状数组直接维护,预处理复杂度 ,单次询问复杂度 ,单次修改复杂度

时与特殊性质 的方法一样做即可,预处理复杂度 ,单次询问复杂度 ,单次修改复杂度

,则复杂度为

对于 ,暴力判断目前这个区间是否被其他区间覆盖。

对于 ,用特殊性质 的方法暴力做即可。

  • 正解

重点是要维护军事基地间的连通性,然后并查集记录连通块大小即可用特殊性质 的方法直接做了。

考虑维护一个连通块中每个元素连接到下一个同连通块内的点。

alt

号点上方的边数。设我们要合并 ()。

,不妨设 ,则我们向右找到第一个位置 满足 ,因为 ,且此时 所在的连通块必定被 所在的连通块包含,故直接合并 。反之同理。

,依旧找到 ,若 则如上操作,否则代表 之间没有其他连通块阻碍了,直接连接即可。

寻找 可以直接线段树上二分。

而合并连通块时分类讨论:

首先定义 表示 所在连通块最左边的位置, 同理。这个直接用并查集维护。

所在连通块无包含关系,则直接给区间 值加一。

否则需要断掉 这条边,加上 这两条边,同样线段树上区间加法维护。

这部分总复杂度

然后维护一下连通块的 之和直接继续做即可。

总复杂度

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace FastIO {
    char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = ' ';
    int p, p3 = -1;
    void read() {}
    void print() {}
    inline int getc() {
        return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
    }
    inline void flush() {
        fwrite(buf2, 1, p3 + 1, stdout), p3 = -1;
    }
    template <typename T, typename... T2>
    inline void read(T &x, T2 &...oth) {
        int f = 0;
        x = 0;
        char ch = getc();
        while (!isdigit(ch)) {
            if (ch == '-')
                f = 1;
            ch = getc();
        }
        while (isdigit(ch)) {
            x = x * 10 + ch - 48;
            ch = getc();
        }
        x = f ? -x : x;
        read(oth...);
    }
    template <typename T, typename... T2>
    inline void print(T x, T2... oth) {
        if (p3 > 1 << 20)
            flush();
        if (x < 0)
            buf2[++p3] = 45, x = -x;
        do {
            a[++p] = x % 10 + 48;
        } while (x /= 10);
        do {
            buf2[++p3] = a[p];
        } while (--p);
        buf2[++p3] = hh;
        print(oth...);
    }
}  // namespace FastIO
#define read FastIO::read
#define print FastIO::print
#define flush FastIO::flush
const int B = 100,A = 100000;
int n,q,c,sid,timer = 0,cnt[100010],a[100010],s[100010];
int LSB(int i){
	return i & (-i);
}
struct Bit{
	int bit[100010];
	void upd(int i,int k){
		if(!i) return;
		while(i <= A){
			bit[i] += k;
			i += LSB(i);
		}
	}
	int psq(int i){
		int s = 0;
		while(i){
			s += bit[i];
			i -= LSB(i);
		}
		return s;
	}
}bt[B + 5];
void modify(int pre,int v){
	for(int i = 1; i <= B; i++){
		if(!cnt[i]) continue;
		bt[i].upd(pre,-pre / i);
		bt[i].upd(v,v / i);
	}
	bt[B + 1].upd(pre,-1);
	bt[B + 1].upd(v,1);
}
namespace DSU{
	int fa[100010],l[100010],r[100010],sz[100010],val[100010];
	void init(){
		for(int i = 1; i <= n; i++) fa[i] = l[i] = r[i] = i,sz[i] = 1,val[i] = a[i];
	}
	int Find(int i){
		return fa[i] == i ? i : fa[i] = Find(fa[i]);
	}	
	void unite(int u,int v){
		int fu = Find(u),fv = Find(v);
		if(fu == fv) return;
		if(sz[fu] > sz[fv]) swap(fu,fv);
		fa[fu] = fv;
		l[fv] = min(l[fu],l[fv]);
		r[fv] = max(r[fu],r[fv]);
		modify(val[fv],val[fu] + val[fv]);
		modify(val[fu],0);
		val[fv] += val[fu];
		val[fu] = 0;
	}
};
using DSU::init;
using DSU::Find;
using DSU::unite;
int mn[400010],tag[400010];
#define ls(i) (i << 1)
#define rs(i) (i << 1 | 1)
void pushup(int i){
	mn[i] = min(mn[ls(i)],mn[rs(i)]);
}
void Tag(int i,int v){
	mn[i] += v,tag[i] += v;
}
void pushdown(int i){
	if(tag[i]){
		Tag(ls(i),tag[i]);
		Tag(rs(i),tag[i]);
		tag[i] = 0;
	}
}
void upd(int i,int l,int r,int ql,int qr,int v){
	if(ql <= l && r <= qr){
		Tag(i,v);
		return;
	}
	pushdown(i);
	int mid = (l + r) >> 1;
	if(ql <= mid) upd(ls(i),l,mid,ql,qr,v);
	if(qr > mid) upd(rs(i),mid + 1,r,ql,qr,v);
	pushup(i);
}
int query(int i,int l,int r,int pos){
	if(l == r) return mn[i];
	pushdown(i);
	int mid = (l + r) >> 1;
	if(pos <= mid) return query(ls(i),l,mid,pos);
	else return query(rs(i),mid + 1,r,pos);
}
int queryl(int i,int l,int r,int pos,int v){
	if(r <= pos){
		if(mn[i] >= v) return 0;
		if(l == r) return l;
		pushdown(i);
		int mid = (l + r) >> 1;
		if(mn[rs(i)] < v) return queryl(rs(i),mid + 1,r,pos,v);
		return queryl(ls(i),l,mid,pos,v);
	}
	pushdown(i);
	int mid = (l + r) >> 1,res = 0;
	if(mid + 1 <= pos) res = queryl(rs(i),mid + 1,r,pos,v);
	if(!res) res = queryl(ls(i),l,mid,pos,v);
	return res; 
}
int queryr(int i,int l,int r,int pos,int v){
	if(pos <= l){
		if(mn[i] >= v) return n + 1;
		if(l == r) return l;
		pushdown(i);
		int mid = (l + r) >> 1;
		if(mn[ls(i)] < v) return queryr(ls(i),l,mid,pos,v);
		return queryr(rs(i),mid + 1,r,pos,v);
	}
	pushdown(i);
	int mid = (l + r) >> 1,res = n + 1;
	if(pos <= mid) res = queryr(ls(i),l,mid,pos,v);
	if(res == n + 1) res = queryr(rs(i),mid + 1,r,pos,v);
	return res;
}
void merge(int x,int y){
	x = Find(x),y = Find(y);
	if(DSU::r[x] < DSU::l[y]) upd(1,1,n,DSU::r[x] + 1,DSU::l[y] - 1,1);
	else upd(1,1,n,max(DSU::l[x],DSU::l[y]),min(DSU::r[x],DSU::r[y]),-1);
	unite(x,y);
}
int OP[100010],U[100010],V[100010],C[100010];
signed main(){
	read(n),read(q),read(sid);
	for(int i = 1; i <= n; i++) read(a[i]);
	for(int i = 1; i <= B; i++){
		for(int j = 1; j <= n; j++) bt[i].bit[a[j]] += (a[j] / i);
		for(int j = 1; j <= A; j++) s[j] = s[j - 1] + bt[i].bit[j];
		for(int j = 1; j <= A; j++) bt[i].bit[j] = s[j] - s[j - LSB(j)];
	}
	for(int i = 1; i <= n; i++) bt[B + 1].bit[a[i]]++;
	for(int i = 1; i <= A; i++) s[i] = s[i - 1] + bt[B + 1].bit[i];
	for(int i = 1; i <= A; i++) bt[B + 1].bit[i] = s[i] - s[i - LSB(i)];
	init();
	for(int i = 1; i <= q; i++){
		read(OP[i]),read(U[i]),read(V[i]);
		if(OP[i] == 2){
			read(C[i]);
			if(U[i] <= B) cnt[U[i]]++;
		}
	}
	int op,u,v;
	for(int t = 1; t <= q; t++){
		op = OP[t],u = U[t],v = V[t];
		if(op == 1){
			if(u > v) swap(u,v);
			int vu = query(1,1,n,u),vv = query(1,1,n,v);
			while(Find(u) != Find(v)){
				if(vu > vv) merge(u,queryr(1,1,n,u,vu)),vu--;
				else if(vu < vv) merge(queryl(1,1,n,v,vv),v),vv--;
				else{
					int pos = queryr(1,1,n,u,vu);
					if(pos < v) merge(u,pos),vu--;
					else merge(u,v);
				}
			}
		}
		else{
			c = C[t];
			swap(c,v);
			int sum = (bt[B + 1].psq(A) - bt[B + 1].psq(u * v - 1)) * v;
			if(u <= B) sum += bt[u].psq(u * v - 1),cnt[u]--;
			else{
				for(int cc = u; cc <= min(A,u * v - 1); cc += u){ //cc ~ min(u * v - 1,cc + u - 1)
					timer++;
					sum += (bt[B + 1].psq(min(u * v - 1,cc + u - 1)) - bt[B + 1].psq(cc - 1)) * (cc / u);
				}
			}
			if(sum >= c * v) printf("Yes\n");
			else printf("No\n");
		}
	}
  	return 0;
}
全部评论
特殊性质B可以做到nsqrt(A)的复杂度
点赞 回复 分享
发布于 2023-10-12 22:30 湖南
@mfeitveer
点赞 回复 分享
发布于 2023-10-12 22:31 湖南
我T4写了个暴力判边相交都过了/cy
点赞 回复 分享
发布于 2023-10-12 22:50 江苏
T3 容斥式子的 ans[j] 不应该是 ANS[j] 吗?
点赞 回复 分享
发布于 2023-10-13 10:18 山东
T4 给出的代码会 MLE
点赞 回复 分享
发布于 2023-10-13 16:07 河北
T3其实不用什么容斥,直接在80pts上判断哪些颜色能选即可,省去 $O(n)$ 的复杂度,总复杂度 $O(3^n\cdot n)$。
点赞 回复 分享
发布于 2023-10-13 18:24 浙江
有没有T1的题解
点赞 回复 分享
发布于 01-24 16:12 广东

相关推荐

10-28 11:04
已编辑
美团_后端实习生(实习员工)
一个2人:我说几个点吧,你的实习经历写的让人觉得毫无含金量,你没有挖掘你需求里的 亮点, 让人觉得你不仅打杂还摆烂。然后你的简历太长了🤣你这个实习经历看完,估计没几个人愿意接着看下去, sdk, 索引这种东西单拎出来说太顶真了兄弟,好好优化下简历吧
点赞 评论 收藏
分享
牛客101244697号:这个衣服和发型不去投偶像练习生?
点赞 评论 收藏
分享
评论
5
收藏
分享
牛客网
牛客企业服务