zoukankan      html  css  js  c++  java
  • CF 1086 题解

    这场 C sb了敲错一个字符爆零了。。最后发现 D 也很简单。

    策略以后还是要先做掉简单题,然后难题调不出来就换题,先大体浏览一下题目再做(说不定后面有我擅长的题呢)

    A

    我们按照 (x) 坐标排序,设 (y) 坐标处于中间位置的点是 (a),另外的点是 (b,c),那么可以先连两条过 (b,c) 垂直于 (a) 所在的水平线上的两条线,然后连一下 (a) 的水平线即可。

    B

    注意到可以设置为 (0) 边,并且直径的两个端点一定是叶子,设叶子有 (l) 个,可以给每个叶子上面的边分配 (frac{s}{l}),答案是 (2frac{s}{l})

    C

    类似数位 dp 枚举卡上界/下界分别卡到哪里,能得到一堆限制区间,现在变成了将区间和区间内的点配对的问题,可以按照左端点排序每次取右端点最小的区间配对。

    但是我把 (n)(k) 写反了。。。要不然早就过了

    D

    发现一个点前后是独立的,分别考虑。

    考虑它的前缀:这个点能胜利要么前面所有的点都和它平局或者被它打败,要么有能打败它的和能被它打败的。证明考虑每一对能打败它的和能被它打败的点即可。

    统计答案的时候我们枚举每种动作,如果一个点合法,当且仅当它的前缀/后缀不会出现仅存在能打败它的动作,但是发现这是若干个区间的交,不是很好做,于是我们容斥后去求不合法的区间的并,用 set 维护一下每种颜色编号最大和最小的位置,树状数组维护一下数字的排名就行了。

    E

    先思考有多少个好的矩阵:这个矩阵要求这一行是上一行的错排,设错排为 (h(n)),那么数量就是 (n!h(n)^{n-1})

    字典序问题肯定是枚举到哪个位置失配,由于第一行没有错排限制所有可以先特判掉。接下来考虑剩下的一般情况:

    假设我们考虑到 ((i,j)) 失配,首先下面的 (n-j) 行方案都是错排,也就是 (h(n)^{n-j}),现在只需要考虑这一行:

    先考虑暴力枚举 (a_{i,j}) 是啥,然后发现这种问题相当于钦定了若干数的错排问题,而这种问题的答案只和长度与有效的限制有关(有效的限制指这个数之前都没被用过,也就是不属于可以随便放的状态)。这里有一个容斥式子可以做,但是需要 NTT。

    发现所有的方案乘上一个置换后是等价的,于是考虑设计 dp (f_{i,j}) 表示长度为 (i)(j) 条限制的方案数。

    那么有转移 (f_{i,j} = f_{i,j-1}-f_{i-1,j-1})(相当于用全部的情况减去违反最后一个限制的情况)

    如果暴力枚举 (a_{i,j}) 是啥能得到一个 (O(n^3)) 的做法过不去,但是发现各种 (a_{i,j}) 的不同之处仅在与它是不是属于一个有效的限制,所以只需要数一下可选的数有多少个是属于有效的限制并且 (< x),多少不是并且 (<x) ,可以用桶+树状数组维护。注意要特判一下这个位置和上一行不能相等即可。

    一类求排名的问题也就是求字典序比它小个数,不要被骗了。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 2000+5;
    const int ha = 998244353;
    int f[MAXN][MAXN];
    
    inline void add(int &x,int y){
    	x += y;if(x >= ha) x -= ha;
    }
    
    // 长度为i的排列 有j个位置限制
    
    inline void prework(){
    	f[0][0] = 0;
    	f[1][0] = 1;f[1][1] = 0;
    	FOR(i,2,MAXN-1){
    		f[i][0] = 1ll*f[i-1][0]*i%ha;
    		FOR(j,1,i){
    			f[i][j] = f[i][j-1];
    			add(f[i][j],ha-f[i-1][j-1]);
    		}
    	}
    }
    
    int n,a[MAXN][MAXN];
    int cnt[MAXN],now;
    
    struct BIT{
    	#define lowbit(x) ((x)&(-(x)))
    	int tree[MAXN];
    	
    	inline void add(int pos,int x){
    		while(pos < MAXN){
    			tree[pos] += x;
    			pos += lowbit(pos);
    		}
    	}
    	
    	inline int query(int pos){
    		int res = 0;if(!pos) return 0;
    		while(pos){
    			res += tree[pos];
    			pos -= lowbit(pos);
    		}
    		return res;
    	}
    }bit1,bit2;
    bool vis[MAXN];
    // 1=无限制
    // 2=有限制
    
    inline int qpow(int a,int n=ha-2){
    	int res = 1;
    	while(n){
    		if(n & 1) res = 1ll*res*a%ha;
    		a = 1ll*a*a%ha;
    		n >>= 1;
    	}
    	return res;
    }
    
    inline void ins(int x,int d){
    	++cnt[x];if(cnt[x] == 2) ++now;
    	if(d){
    		if(cnt[x] == 1) bit1.add(x,1);
    		else bit2.add(x,1);
    	}
    	else{
    		if(cnt[x] == 2) bit1.add(x,-1),bit2.add(x,1);
    	}
    }
    
    int main(){
    	prework();
    	scanf("%d",&n);FOR(i,1,n) FOR(j,1,n) scanf("%d",&a[i][j]);
    	// TODO: 第一行特判
    	int ans = 0;
    	ROF(i,n,1){
    		// 第一行没有错排限制!
    		int c = bit1.query(a[1][i]);
    		if(c) add(ans,1ll*c*f[n-i][0]%ha);
    		bit1.add(a[1][i],1);
    	}
    	// DEBUG(ans);
    	ans = 1ll*ans*qpow(f[n][n],n-1)%ha;
    	FOR(i,2,n){
    		CLR(cnt,0);CLR(vis,0);CLR(bit1.tree,0);CLR(bit2.tree,0);now = 0;
    		int gx = 0;
    		ROF(j,n,1){
    			/*
    			首先要计算出前i个中有多少数字是相同的k,那么后面的数有j-k个没有限制
    			然后枚举这个位置填什么 情况只有填都有的和没有的
    			*/
    			// 现在需要知道 当前的位置 有多少个数字 有限制/无限制
    			ins(a[i-1][j],0);ins(a[i][j],1);
    			int t1 = bit1.query(a[i][j]-1),t2 = bit2.query(a[i][j]-1),sz = n-j+1;
    			if(cnt[a[i-1][j]] == 2 && a[i-1][j] < a[i][j]) t2--;
    			// if(j == 2)DEBUG(sz-1),DEBUG(now);	
    			add(gx,1ll*t1*f[sz-1][now-(cnt[a[i-1][j]]==2)]%ha);
    			add(gx,1ll*t2*f[sz-1][now-1-(cnt[a[i-1][j]]==2)]%ha);
    		}
    		// if(i == 2) exit(0);
    		gx = 1ll*gx*qpow(f[n][n],n-i)%ha;
    		add(ans,gx);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    /*
    每一行是以上一行为基的错排 设错排为 h(n)
    有多少个好矩阵? h(n)^n
    枚举第i行开始不同 后面都是错排方案数
    然后枚举 (i,j) 格子不同,如果能枚举这个格子填的是啥,相当于就是一个固定数字的错排问题了
    */
    

    F

    这个东西显然是不能直接算的,但是我们发现设 (f(x)) 表示 (x) 时刻覆盖面积大小还是可以算的,考虑转化到这上面。

    首先有一个经典的考虑贡献的转化:设 (t_i) 表示 (i) 点被燃烧的时间,那么 (sum t_i = sum (t-t_i) = tf(t)-sum_{i=0}^{t-1}f(i))。所以现在的目标是如何求 (sum_{i=0}^{t-1}f(i))

    剩下的我也想不到。。对于 (t) 这么大肯定要考虑插值之类的,首先容斥原理变成求交,我们考虑两个点的交的情况:首先在没有触碰的时候一直是 (0),之后就是一个二次函数。所以相当于在每两个矩形开始相碰的时候就要加上一个二次函数,也就是在这里分段,整体形状是分段二次函数。而将分段二次函数段内线性组合不会影响函数的形态,所以整个 (f(i)) 就是分段二次函数,段都分在某个时间点两个矩形开始相交的时候。然后对每一段插个值就行了。

    这里不得不提一下线段树求矩形并或者离散化维护东西的细节:必须要让区间是左闭右开/左开右闭区间才不会算重,可以好好看看代码。。写了一晚上

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int ha = 998244353;
    const int inv2 = 499122177;
    const int inv6 = 166374059;
    const int MAXN = 300+5;
    std::vector<LL> Sx,Sy;
    
    namespace DS{
    	int sm[MAXN<<2],tag[MAXN<<2];
    	#define lc ((x)<<1)
    	#define rc ((x)<<1|1)
    	
    	inline void modify(int x,int l,int r,int L,int R,int d){
    		// int len = Sy[r-1]-(l==1?Sy[0]:Sy[l-2]);
    		int len = Sy[r]-Sy[l-1];
    		// if(l == 1 && r == 2) DEBUG(Sy[r]),DEBUG(Sy[l-1]);
    		// DEBUG(l);DEBUG(r);DEBUG(len);
    		if(l == L && r == R){
    			tag[x] += d;
    			sm[x] = tag[x] ? len : (l==r?0:(sm[lc]+sm[rc])%ha);
    			return;
    		}
    		int mid = (l + r) >> 1;
    		if(R <= mid) modify(lc,l,mid,L,R,d);
    		else if(L > mid) modify(rc,mid+1,r,L,R,d);
    		else modify(lc,l,mid,L,mid,d),modify(rc,mid+1,r,mid+1,R,d);
    		sm[x] = tag[x]?len:(l==r?0:(sm[lc]+sm[rc])%ha);
    	}
    }
    using DS::modify;
    
    int x[MAXN],y[MAXN],n;
    
    inline void add(int &x,int y){
    	x += y;if(x >= ha) x -= ha;
    }
    
    struct Node{
    	int x1,y1,x2,y2;//(x1,y1) <= (x2,y2)
    	Node(int x1=0,int y1=0,int x2=0,int y2=0) : x1(x1),y1(y1),x2(x2),y2(y2) {}
    }a[MAXN];
    
    std::vector<std::pair<P,int> > G[MAXN];
    
    inline int f(int k){
    	Sx.clear();Sy.clear();
    	FOR(i,1,n){
    		a[i] = Node(x[i]-k,y[i]-k,x[i]+k+1,y[i]+k+1);
    		Sx.pb(x[i]-k);Sy.pb(y[i]-k);Sx.pb(x[i]+k+1);Sy.pb(y[i]+k+1);
    		// DEBUG(x[i]+k+1);DEBUG(y[i]+k+1);
    	}
    	std::sort(all(Sx));Sx.erase(std::unique(all(Sx)),Sx.end());
    	std::sort(all(Sy));Sy.erase(std::unique(all(Sy)),Sy.end());
    	// Sx.pb(Sx[0]-1);Sy.pb(Sy[0]-1);
    	// std::sort(all(Sx));std::sort(all(Sy));
    	int m = Sx.size(),M = Sy.size();
    	FOR(i,0,m+1) G[i].clear();
    	FOR(i,1,n){
    		a[i].x1 = std::lower_bound(all(Sx),a[i].x1)-Sx.begin()+1;
    		a[i].x2 = std::lower_bound(all(Sx),a[i].x2)-Sx.begin()+1;
    		a[i].y1 = std::lower_bound(all(Sy),a[i].y1)-Sy.begin()+1;
    		a[i].y2 = std::lower_bound(all(Sy),a[i].y2)-Sy.begin()+1;
    		// printf("%d %d %d %d
    ",a[i].x1,a[i].y1,a[i].x2,a[i].y2);
    		G[a[i].x1].pb(MP(MP(a[i].y1,a[i].y2),1));
    		G[a[i].x2].pb(MP(MP(a[i].y1,a[i].y2),-1));
    	}
    	Sy.pb(Sy.back());
    	// Sy.pb(Sy[0]-1);std::sort(all(Sy));
    	// exit(0);
    	CLR(DS::sm,0);CLR(DS::tag,0);
    	int ans = 0;
    	FOR(i,1,m){
    		int t = (Sx[i-1]-Sx[std::max(i-2,0)])%ha;
    		// DEBUG(t);
    		// DEBUG(DS::sm[1]);
    		add(ans,1ll*t*DS::sm[1]%ha);
    		for(auto x:G[i]) modify(1,1,M,x.fi.fi,x.fi.se-1,x.se);//,DEBUG(x.fi.fi),DEBUG(x.fi.se-1),DEBUG(x.se);
    	}
    	return ans;
    }
    
    inline int sm2(int n){
    	int res = 0;if(n < 0) return 0;
    	if(n <= 5){
    		FOR(i,1,n) add(res,i*i);
    	}
    	else{
    		res = 1ll*n*(n+1)%ha*(2ll*n+1)%ha*inv6%ha;
    	}
    	return res;
    }
    
    inline int sm(int n){
    	int res = 0;if(n < 0) return 0;
    	if(n <= 5){
    		FOR(i,1,n) add(res,i);
    	}
    	else{
    		res = 1ll*n*(n+1)%ha*inv2%ha;
    	}
    	return res;
    }
    
    inline int sm(int l,int r){
    	return (sm(r)+ha-sm(l-1))%ha;
    }
    
    inline int sm2(int l,int r){
    	return (sm2(r)+ha-sm2(l-1))%ha;
    }
    
    int main(){
    	int t;scanf("%d%d",&n,&t);
    	// t = 100000;
    	FOR(i,1,n) scanf("%d%d",x+i,y+i);
    	std::vector<int> part;part.pb(0);part.pb(t);
    	FOR(i,1,n){
    		FOR(j,i+1,n){
    			int d = std::max(std::abs(x[i]-x[j]),std::abs(y[i]-y[j]));
    			d = (d+1)/2;
    			if(d >= t) continue;
    			part.pb(d);
    			// if(d-1 > 0) part.pb(d-1);
    			// if(d+1 < t) part.pb(d+1);
    		}
    	}
    	// DEBUG(f(t));
    	// exit(0);
    	// DEBUG(1ll*(400000000ll+1)*(400000000+1)%ha);
    	// exit(0);
    	// int ttt = 0;
    	// FOR(i,0,t-1) add(ttt,f(i));
    	// DEBUG(ttt);
    	std::sort(all(part));part.erase(std::unique(all(part)),part.end());
    	int ans = 0;
    	FOR(i,1,(int)part.size()-1){
    		int l = part[i-1],r = part[i]-1;
    		// DEBUG(l);DEBUG(r);
    		if(r-l+1 <= 3){
    			FOR(k,l,r) add(ans,f(k));
    			continue;
    		}
    		int x[3],y[3];
    		FOR(j,0,2) y[j] = f(x[j]=l+j);
    		int a = 1ll*y[0]*inv2%ha;
    		add(a,ha-y[1]);add(a,1ll*y[2]*inv2%ha);
    		int b = 1ll*y[0]*inv2%ha*(x[1]+x[2])%ha;
    		add(b,ha-1ll*y[1]*((x[0]+x[2])%ha)%ha);
    		add(b,1ll*y[2]*inv2%ha*(x[1]+x[0])%ha);
    		b = ha-b;
    		
    		int c = 1ll*x[1]*x[2]%ha*y[0]%ha*inv2%ha;
    		add(c,ha-1ll*x[0]*x[2]%ha*y[1]%ha);
    		add(c,1ll*x[0]*x[1]%ha*y[2]%ha*inv2%ha);
    		// DEBUG(a);DEBUG(b);DEBUG(c);
    		// DEBUG(sm(l,r));
    		// int tt = 0;
    		// FOR(i,l,r) add(tt,i);
    		// DEBUG(tt);
    		add(ans,1ll*sm2(l,r)*a%ha);
    		// DEBUG(sm2(l,r));
    		add(ans,1ll*sm(l,r)*b%ha);
    		add(ans,1ll*(r-l+1)*c%ha);
    	}
    	// DEBUG(ans);
    	// DEBUG(ans);
    	// int _ = 400000000ll*400000000%ha;
    	add(ans,ha-1ll*t*f(t)%ha);
    	ans = (ha-ans)%ha;
    	printf("%d
    ",ans);
    	return 0;
    }
    /*
    设 f(i) 表示 i 时间有多少个格子着火了
    答案就是 tf(t)-sum_{i=0}^{t-1}f(i)
    单次询问 f(i) 是可以求矩阵面积并的,但是你不能询问1e8次
    考虑用容斥原理将矩形面积并变成交
    观察一个子集 发现只有在某两个矩形由不交变成相交的时候才会分段
    所以只有 n^2 段 插值 每一段需要做3/4次矩形面积并
    矩形面积并就是离散化后排序 支持区间加删线段 全局查询 修改到对应线段即可
    */
    
  • 相关阅读:
    ajaxFileUpload 实现多文件上传(源码)
    Springboot 热部署的两种方式
    基于树莓派3B+Python3.5的OpenCV3.4的配置教程
    Shiro 架构原理
    Cron表达式
    SpringBoot中Scheduled代码实现
    Linus安装mysql8
    查看虚拟机CENTOS7 的 IP 地址和命令
    linux vi保存退出命令 (如何退出vi)
    Linux常用命令大全
  • 原文地址:https://www.cnblogs.com/rainair/p/14305867.html
Copyright © 2011-2022 走看看