【noip模拟】Div.1

11.17 保险箱

结论:合法的数 \(x|\gcd(n,a_{k}),x\nmid a_{i}(i<k)\)。否则由裴蜀定理得 \(ix\mod n(0\le i<n)\) 互不相同,即所有的数都合法,显然矛盾。

重点在于实现。先把 \(\gcd(n,a_{k})\) 的约数、质因子取出来,然后把 \(\gcd(a_{i},n,a_{k})(i<k)\) 的约数删去。删的时候可以记忆化:如果当前数 \(x\) 未被删去,删去 \(x\) 并尝试删 \(x\) 去掉一个质因子后的数;否则结束。时间复杂度 \(O(\sqrt{n}+k\log n+d(n)\omega(n))\)

code
const int N = 2.5e5+5;
int m,pri;
LL n,d,x,a[N],p[N];
set<LL> ans;

void ban(LL x) {
	if( !ans.count(x) ) return;
	ans.erase(ans.find(x));
	For(i,1,pri) if( !(x % p[i]) ) ban(x/p[i]);
}

signed main() {
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	read(n,m); For(i,1,m) read(a[i]);
	d = x = __gcd(n,a[m]);
	for(LL i = 2; i*i <= x; ++i) if( !(x % i) ) {
		p[++pri] = i;
		while( !(x % i) ) x /= i;
	} if( x > 1 ) p[++pri] = x;
	for(LL i = 1; i*i <= d; ++i) if( !(d % i) ) ans.insert(i), ans.insert(d/i);
	Rep(i,1,m) ban(__gcd(a[i],d));
	write(n/(*ans.begin()));
	return ocl();
}

11.16 法阵

出题人:原题 \(3100\),张口放 T1

11.11 糖

贪心题还是做的太少了,考场上啥想法都没有。

比较显然的 DP 时设 \(f[i,j]\) 为到第 \(i\) 个城市时拥有 \(j\) 个糖果的最小花费,但状态数就是 \(O(nC)\) 的,不可做。
需要 DP 的原因是对于一个糖果不知道它的“命运”,无法确定决策进行贪心,分类讨论一下(在 \(i\) 处被买):

  1. 消耗在路上。要让花费最小,应该优先消耗购买价低的
  2. \(j\) 处卖出。如果存在 \(i<k<j(buy_{i}<sell_{k}<sell_{j})\),那么不应该在 \(k\) 处卖,但等价于在 \(k\) 处卖出,然后多了一个购买价为 \(sell_k\) 的糖果
  3. 没用。应该不买它,但等价于到达城市 \(j(buy_{j}<buy_{i})\) 时换成买 \(j\),或在到达 \(n\) 后把剩下的糖果按购买价卖出(rnm,退钱

考虑决策包容性:从每个城市离开时可以带满 \(C\) 个糖果(其中可能有从上一个城市带过来的),依次进行模拟这些情况:

  1. 挑购买价最低的 \(a_{i+1}-a_{i}\) 的糖果消耗掉
  2. 把购买价小于 \(sell_{i}\) 的糖果卖掉,并凭空多出购买价为 \(sell_{i}\) 的这么多个糖果
  3. 把购买价大于 \(buy_{i}\) 的糖果退掉
  4. \(buy_{i}\) 买糖果至 \(C\)

实现时用单调队列按购买价递增的顺序维护剩余糖果即可,代码中采取了费用延迟计算,即使用一个糖果时再买,那么单调队列记录的其实是能买的糖果

code
const int N = 2e5+5;
int n,m,a[N],b[N],c[N];
LL ans;
int l=1,r; PII q[N]; // <price,number>

signed main() {
	freopen("candy.in","r",stdin);
	freopen("candy.out","w",stdout);
	read(n,m); For(i,1,n) read(a[i]); Rep(i,0,n) read(b[i],c[i]);
	rFor(i,n,1) a[i] -= a[i-1];
	Rep(i,0,n, inq = 0) {
		while( l <= r && q[r].fi > b[i] ) inq -= q[r--].se;
		int cnt = 0;
		while( l <= r && q[l].fi < c[i] )
			ans += LL(q[l].fi-c[i]) * q[l].se, cnt += q[l++].se;
		if( cnt ) q[--l] = MP(c[i],cnt);
		if( inq < m ) q[++r] = MP(b[i],m-inq); inq = m;
		while( a[i+1] ) {
			int use = min(a[i+1],q[l].se);
			ans += (LL)q[l].fi * use;
			a[i+1] -= use, q[l].se -= use, inq -= use;
			if( !q[l].se ) ++l;
		}
	}
	write(ans);
	return ocl();
}

11.5 混乱邪恶

完全不知道如何下手,感觉题目给的保证都莫名奇妙的。。。

通过添加 \(a=0\) 规约到 \(n\) 为偶数的情况,设 \(d_{i}=a_{2i}-a_{2i-1}\),则 \(\sum_{i=1}^{n}d_{i}\le m-\frac{n}{2}<n\) 且为偶数。问题转化为调整 \(d\) 的正负来让 \(\sum_{i=1}^{n}d_{i}=0\)

归纳证明若 \(\sum_{i=1}^{n}d_{i}<2n\) 且为偶数则一定有解:

  • \(n=1\) 显然成立
  • \(\exists d_{i}=0\),则去掉该 \(d_{i}\) 并转化为 \(n-1\) 的情况
  • \(n-1\) 成立,对于 \(n\),可以令 \(\max\{d\}\) 为正、\(\min\{d\}\) 为负,删除这两数并添加 \(\max\{d\}-\min\{d\}\),和减少了 \(2\min\{d\}\) 即至少为 \(2\),仍满足条件,转化为了 \(n-1\) 的情况

实现上不需要模拟这个过程,先将 \(d\) 都取正,排序后从大到小能取负则取负即可

10.20 偶数

10.9 T4 显然也是我整的

9.23 打字机

先明确一点:不能直接算 LCS 然后加加减减,因为丢失了位置信息。比如 abc -> abd 的答案是 \(2\)

不过可以得出一个类似 LCS 的暴力 DP:设 \(f[i,j]\) 表示编辑到 \(s\)\(i\) 个字符,\(t\)\(j\) 个的答案,转移时考虑三种操作:\(f[i,j]=\min(f[i-1,j]+1,f[i,j-1]+1,f[i-1,j-1]+[s_i\ne t_j])\),时间复杂度 \(O(m|s||t|)\)
发现这个 DP 并没有利用 \(t\) 不变的性质,每次重新 DP 导致复杂度无法接受。重新设 \(f[i,j,k]\) 表示 \(s[i:j]\)\(t[1:k]\) 的答案,复杂度变成优秀的 \(O(|s|^2|t|+m)\)

继续找性质,对于每个 \(j\),设 \(h(i)\) 表示 \(s[j-i+1:j]\)(长度为 \(i\) 的后缀)的答案,则 \(i-h(i)\) 是单调不降的(\(i\)\(+1\)\(h(i)\) 最多 \(+1\)),且 \(-|t|\le i-h(i)\le|t|\)
发现暴力 DP 中 \(f[i,j,k]\) 保存的就是 \(h(j-i+1)\),考虑调换定义域和值域:改设 \(f[i,j,k]\)\(s[1:i],t[1:j]\) 时满足 \(len-h(len)\le k\) 的最大的 \(len\)(对于 \(l\in[i-len+1,i],r=i\) 的询问,答案 \(\ge r-l+1-k\)
转移通过 \(len-h(len)\le k\) 这个式子魔改暴力 DP 即可

梳理一下:

  • 初值:\(f[i,j,-|t|-1\cdots-j-1]=f[0,0,-|t|-1\cdots-1]=-1\)\(f[0,0,0\cdots|t|]=0\),其余为 \(\inf\)
  • 转移:\(f[i,j,k]=\min(f[i-1,j,k]+1,f[i,j-1,k+1],f[i-1,j-1,k-[s_i=t_j]]+1)\)
  • 询问 \([l,r]\):找到最小的 \(k\) 使得 \(f(r,|t|,k)\ge r-l+1\),答案为 \(r-l+1-k\)

注意细节

时间复杂度 \(O(|s||t|^2)\),单次询问可以二分做到 \(\log|t|\)

9.18 高考

9.12 万猪拱塔

用 DS 直接维护矩形没有前途(比如我)

发现极差等于矩形面积减一,因此每个合法的矩形对应了唯一的权值区间 \([l,r]\)(对答案贡献 \(r-l+1\)),考虑在移动 \(r\) 时维护 \(l\)

将权值在 \([l,r]\) 范围内的格子染黑,将每个 \(2\times2\) 个格子组成的正方形记为一块(部分超出边界也算,共 \((n+1)(m+1)\) 个)。那么 \([l,r]\) 合法的充要条件为:不存在包含 \(3\) 个黑格的块,仅有 \(4\) 个包含 \(1\) 个黑格的块(四个角),等价于有且仅有 \(4\) 个包含奇数个黑格的块。

考虑线段树维护每个 \(l\),在当前 \(r\)\([l,r]\) 中有多少个包含奇数个黑格的块(记为 \(f(l)\)),一定有 \(f(r)=4\),因此线段树只要维护 \(\min\{f(l)\}\)\(f(l)\) 取到最小值时 \(l\) 的个数、\(l\) 的和即可快速计算答案。
\(r\leftarrow r+1\) 时只会影响 \(4\) 个块,将变化的 \(f(l)\) 在线段树上区间修改即可。

code
#define id(x,y) (((x))*(m+1)+(y))
const int N = 4e5+5, mod = 998244353;
int n,m,a[N];
PII p[N];

LL ans;

#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)
#define lson ls,l,mid
#define rson rs,mid+1,r
#define up(u) t[u]=t[ls]+t[rs]
int tag[N*4];
struct Node { int mn; LL cnt,sum; } t[N*4];
Node operator + (const Node &x,const Node &y) {
	if( x.mn == y.mn ) return Node{x.mn,x.cnt+y.cnt,(x.sum+y.sum)%mod};
	return x.mn<y.mn ? x : y;
}
void build(int u=1,int l=1,int r=n*m) {
	if( l == r ) { t[u] = Node{N,1,l}; return; }
	build(lson), build(rson), up(u);
}
void down(int u,int x) { t[u].mn += x, tag[u] += x; }
void down(int u) { down(ls,tag[u]), down(rs,tag[u]), tag[u] = 0; }
void add(int ql,int qr,int x,int u=1,int l=1,int r=n*m) {
	if( ql > qr || qr < l || r < ql ) return;
	if( ql <= l && r <= qr ) return down(u,x);
	down(u), add(ql,qr,x,lson), add(ql,qr,x,rson), up(u);
}
#undef ls
#undef rs
#undef lson
#undef rson
#undef up

void modify(int r,int x,int y) {
	static int b[6];
	b[1] = a[id(x,y)], b[2] = a[id(x,y+1)],
	b[3] = a[id(x+1,y)], b[4] = a[id(x+1,y+1)];
	sort(b+1,b+5);
	int m = 1; while( b[m] != r ) ++m;
	For(i,1,m) add(b[i-1]+1,b[i], (m-i+1)&1?1:-1 );
}

signed main() {
	freopen("pig.in","r",stdin);
	freopen("pig.out","w",stdout);
	read(n,m);
	For(i,1,n) For(j,1,m) read(a[id(i,j)]), p[a[id(i,j)]] = MP(i,j);
	build();
	For(i,1,n*m) {
		add(i,i,-N);
		modify(i,p[i].fi-1,p[i].se-1), modify(i,p[i].fi-1,p[i].se);
		modify(i,p[i].fi,p[i].se-1), modify(i,p[i].fi,p[i].se);
		ans += (t[1].cnt * i - t[1].sum + t[1].cnt) %mod;
	}
	write((ans%mod+mod)%mod);
	return iocl();
}

9.9 第负二题

\(f_i\) 的数学意义:中心在第 \(i\) 行的全 \(1\) 组成的最大正方形(对角线水平/竖直),对角线长 \(2f_i-1\)

显然 \(f_i\) 具有单调性(存在较大的正方形则一定存在更小的)。由此得到一个朴素做法:
对于每行二分答案 \(k\),判断是否合法即判断是否存在合法的中心:

\[\max\{l_j+k-1+(i-j)\}\le\min\{r_j-k+1-(i-j)\},j\le i \]

\[\max\{l_j+k-1+(j-i)\}\le\min\{r_j-k+1-(j-i)\},j\ge i \]

发现两个 \(\max,\min\) 中与 \(j\) 相关的分别为 \(l_j-j,r_j+j,l_j+j,r_j-j\),RMQ 即可。
时间复杂度 \(O(n\log n)\)

另一个结论是 \(f_{i-1}-1\le f_i\le f_{i-1}+1\)(第 \(i-1\) 行变为 \(0\) 的下一时刻第 \(i\) 行一定都是 \(0\))。因此 \(k\) 不用二分,从 \(f_{i-1}-1\) 开始枚举即可,如果当前 \(k\) 不合法,则 \(f_i=k-1\),否则 \(k\leftarrow k+1\)

考虑单调队列做到 \(O(n)\) RMQ。
对于 \(j\ge i\) 的情况,第 \(i-1\)\(j\) 的区间为 \([i-1,(i-1)+(k+1)-1]\),扩展到第 \(i\) 行时变为 \([i,i+k-1]\)\(i,k\) 变大时左右端点都单调,普通单调队列。
对于 \(j\le i\),区间为 \([(i-1)-(k+1)+1,i-1]\) 变到 \([i-k+1,i]\),左端点不具有单调性。发现 \(k\) 最多变大两次,因此强制令左端点单调,对于没有考虑到的点(最多两个)特判即可。

code
const int N = 5e6+5, mod = 998244353;
int L,X,Y;
ULL A,B;

int n,m,l[N],r[N],f[N];
LL ans;

struct Que {
	int fro,rea,que[N];
	Que() { fro = 1, rea = 0; }
	int front() { return que[fro]; }
	int back() { return que[rea]; }
	void push_back(int x) { que[++rea] = x; }
	void pop_back() { --rea; }
	bool empty() { return fro>rea; }
	void valid(int l) { while( fro<=rea && que[fro] < l ) ++fro; }
} ul,ur,dl,dr;

namespace data {
typedef unsigned long long u64;
u64 xorshift128p(u64 &A, u64 &B) { u64 T = A, S = B; A = S; T ^= T << 23;
	T ^= T >> 17; T ^= S ^ (S >> 26); B = T; return T + S; }
void gen(int n, int L, int X, int Y, u64 A, u64 B, int l[], int r[]) { 
	for (int i = 1; i <= n; i ++) { l[i] = xorshift128p(A, B) % L + X; 
		r[i] = xorshift128p(A, B) % L + Y; if (l[i] > r[i]) swap(l[i], r[i]); } }
}

signed main() {
	read(n,L,X,Y,A,B); data::gen(n,L,X,Y,A,B,l,r);
	f[1] = 1;
	For(i,2,n) For(k,max(f[i-1],2),f[i-1]+2) { int j = i+k-1;
		ul.valid(i-k+1), ur.valid(i-k+1), dl.valid(i), dr.valid(i);
		while( !ul.empty() && l[i]+i >= l[ul.back()]+ul.back() ) ul.pop_back();
		while( !ur.empty() && r[i]-i <= r[ur.back()]-ur.back() ) ur.pop_back();
		while( !dl.empty() && l[j]-j >= l[dl.back()]-dl.back() ) dl.pop_back();
		while( !dr.empty() && r[j]+j <= r[dr.back()]+dr.back() ) dr.pop_back();
		ul.push_back(i), ur.push_back(i), dl.push_back(j), dr.push_back(j);
		int le = l[ul.front()]+ul.front()-i+k-1,
			ri = r[ur.front()]-ur.front()+i-k+1;
		ckmax(le,l[dl.front()]-dl.front()+i+k-1),
		ckmin(ri,r[dr.front()]+dr.front()-i-k+1);
		if( k > f[i-1] )
			ckmax(le,l[i-k+1]+i-k+1-i+k-1), ckmin(ri,r[i-k+1]-(i-k+1)+i-k+1);
		if( k > f[i-1]+1 )
			ckmax(le,l[i-k+2]+i-k+2-i+k-1), ckmin(ri,r[i-k+2]-(i-k+2)+i-k+1);
		if( le > ri ) { f[i] = k-1; break; }
	}
	for(LL i = 1, pw = 1; i <= n; ++i, pw = pw*3%mod) ans += pw * f[i] %mod;
	write(ans%mod);
	return iocl();
}

posted @ 2021-09-11 09:50  401rk8  阅读(176)  评论(0编辑  收藏  举报