2024.7 - 做题记录与方法总结

2024/07/01#

AtCoder Beginner Contest 360#

E - Random Swaps of Balls#

期望 \(dp\)

问题陈述

\(N - 1\) 个白球和一个黑球。这些 \(N\) 个球排成一排,黑球最初位于最左边的位置。

高桥正好要进行下面的操作 \(K\) 次。

  • \(1\)\(N\) 之间均匀随机地选择一个整数,包括两次。设 \(a\)\(b\) 为所选整数。如果是 \(a \neq b\) ,把左边的 \(a\) -th 和 \(b\) -th 两个球交换。

经过 \(K\) 次操作后,让黑球位于左边的 \(x\) -th 位置。求 \(x\) 的期望值,模为 \(998244353\)

模数 \(998244353\) 的期望值是多少?
可以证明所求的期望值总是有理数。此外,在本题的限制条件下,还可以证明如果用不可约分数 \(\frac{P}{Q}\) 表示这个值,那么就是 \(Q \not \equiv 0 \pmod{998244353}\) 。因此,存在一个唯一的整数 \(R\) ,使得 \(R \times Q \equiv P \pmod{998244353}, 0 \leq R \leq 998244353\) 。报告这个 \(R\) .

参考了 Lanly 的题解

首先,我们可以看到:对于每一个不为 \(1\) 位置,其价值相等

那么,我们自然设置出状态:

  1. 其在 \(1\) 号位上
  2. 其不在 \(1\) 号位上

对于第 \(i\) 次操作,位于 \(1\) 号位的期望为 \(dp_{i,0}\), 位于非 \(1\) 号位的期望为 \(dp_{i,1}\)

那么,黑球惨遭移动的概率为 $m = 2\frac{1}{n} \cdot \frac{n-1}{n} = \frac{2(n-1)}{n^2} $

故,黑球纹丝不动的概率为 \(s = 1 - m\)

黑球移动到某一个位置的概率为 \(to = \frac{move}{n - 1} = \frac{2}{n^2}\)

转移为:

\[dp_{i,0} = s \cdot dp_{i-1,0} + to \cdot dp_{i-1,1} \\ dp_{i,1} = m \cdot dp_{i-1,0} + (1 - to) \cdot dp_{i-1,1} \]

最后,因为 \(p_1 = dp_{k,0} \ p_2 = p_3 = p_4 = ... = p_n = \frac{dp_{k,1}}{n-1}\)

由期望计算公式

\[E(X) = \sum_{i = 1}^n ip_i \]

本题的答案

\[ans = \sum_{i = 1}^n ip_i = dp_{k,0} + \frac{dp_{k,1}}{n-1}\sum_{i = 2}^{n} i = dp_{k,0} + \frac{(n + 2)(n-1)}{2} \cdot \frac{dp_{k,1}}{n-1} = \frac{(n + 2)dp_{k,1}}{2} + dp_{k,0} \]

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int mod = 998244353;

int qpow(int x,int k) {
	int res = 1;
	while(k) {
		if(k & 1) res = (res * x) % mod;
		x = (x * x) % mod;
		k >>= 1;
	}
	return res;
}

int inv(int x) {
	return qpow(x,mod - 2) % mod;
}

int dp[100005][2];

signed main() {

	int n = rd(),k = rd();

	if(n == 1) {
		wt(1);
		return 0;
	}

	dp[0][0] = 1;

	int P = inv(n);
	int P2 = (P * P) % mod;

	int move = ((n-1)<<1) % mod * P2 % mod;
	int stay = (1 - move + mod) % mod,to = (P2<<1) % mod;

	for(int i = 1;i<=k;i++) {
		dp[i][0] = (dp[i-1][0] * stay % mod + dp[i-1][1] * to % mod) % mod;
		dp[i][1] = (dp[i-1][0] * move % mod + dp[i-1][1] * ((1- to + mod) % mod) %mod) % mod;
	}
	
	int ans = dp[k][0] + ((n + 2) * (n - 1) / 2) % mod * dp[k][1] % mod * inv(n-1) % mod;

	wt(ans % mod);

	return 0;
}

G - Suitable Edit for LIS#

问题陈述

给你一个长度为 \(N\) 的整数序列 \(A\) 。高桥将执行下列操作一次:

  • \(1\)\(N\) 之间选择一个整数 \(x\) ,以及一个任意整数 \(y\) 。将 \(A_x\) 替换为 \(y\)

求操作后 \(A\) 的最长递增子序列(LIS)的最大可能长度。

什么是最长递增子序列?

序列 \(A\) 的子序列是从 \(A\) 中抽取一些元素而不改变顺序得到的序列。

序列 \(A\) 的最长递增子序列是严格递增的 \(A\) 的最长子序列。

看到题目,自然想到预处理出 \(pre,suf\) 用来记录 \(i\) 位前后,以 \(i\) 为 终点/起点 的 LIS,最后拼起来

前置:P1439 【模板】最长公共子序列

图方便就用 unordered_map <int,int> 来充当 树状数组

前后各遍历一次,就可求出 \(pre,suf\) 数组

剩下的参考 SJH_qwq 的 题解

AC-code:

#include<bits/stdc++.h>
using namespace std;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int inf = 1e9+7;
struct BIT{
#define lowbit(x) (x &(-x))
unordered_map<int,int> c;

void add(int x,int d){
	while(x <= inf) {
		c[x] = max(c[x],d);
		x += lowbit(x);
	}
}

int query(int x){
	int res = 0;
	while(x) {
		res = max(res,c[x]);
		x -= lowbit(x);
	}
	return res;
}

}t;

signed main() {

	int n = rd();
	vector<int> a(n + 2),r(n + 2),l(n + 2);
	for(int i = 1;i<=n;i++) a[i] = rd();
	int ans = 0;
	for(int i = 1;i<=n;i++) {
		r[i] = t.query(a[i] - 1) + 1;
		t.add(a[i],r[i]);
		ans = max(ans,r[i] + (i != n));
	}
	t.c.clear();
	for(int i = n;i >= 1;i--) {
		l[i] = t.query(inf - a[i] - 1) + 1;
		t.add(inf - a[i],l[i]);
		ans = max(ans,l[i] + (i != 1));
	}
	t.c.clear();
	for(int i = 1;i<=n;i++) {
		int k = t.query(a[i + 1]);
		ans = max(ans,l[i + 1] + k + 1);
		t.add(a[i] + 2,r[i]);
	}

	wt(ans);


	return 0;
}

2024/07/08#

P4587 [FJOI2016] 神秘数#

题面:

题目描述

一个可重复数字集合 \(S\) 的神秘数定义为最小的不能被 \(S\) 的子集的和表示的正整数。例如 \(S=\{1,1,1,4,13\}\),有:\(1 = 1\)\(2 = 1+1\)\(3 = 1+1+1\)\(4 = 4\)\(5 = 4+1\)\(6 = 4+1+1\)\(7 = 4+1+1+1\)

\(8\) 无法表示为集合 \(S\) 的子集的和,故集合 \(S\) 的神秘数为 \(8\)

现给定长度为 \(n\)正整数序列 \(a\)\(m\) 次询问,每次询问包含两个参数 \(l,r\),你需要求出由 \(a_l,a_{l+1},\cdots,a_r\) 所组成的可重集合的神秘数。

输入格式

第一行一个整数 \(n\),表示数字个数。

第二行 \(n\) 个正整数,从 \(1\) 编号。

第三行一个整数 \(m\),表示询问个数。

输出格式

对于每个询问,输出一行对应的答案。

样例 #1
样例输入 #1
5
1 2 4 9 10
5
1 1
1 2
1 3
1 4
1 5
样例输出 #1
2
4
8
8
8
提示

对于 \(100\%\) 的数据点,\(1\le n,m\le {10}^5\)\(\sum a\le {10}^9\)

可持久线段树的典题

我们假设已经知道 可重集 \(\{a | a \in A[l,r]\}\) 中的数字可以组成的数的最小连续范围为 \([0,maxn]\)

我们向可重集里插入一个数 \(a_{r + 1}\) ,观察有什么情况

如果 $a_{r + 1} \in [0,maxn + 1] $,因为可以通过给 \(a_{r + 1}\) 加上 \([1,maxn]\) 中的任意一个数 ,将可重集扩展到 \([0,maxn + a_{r + 1}]\)

如果 \(a_{r + 1}\not \in [0,maxn + 1]\) ,在 \(maxn + 1\) 处必然有个缺口,致使答案为 \(maxn + 1\)

令缺口值为 \(ans = maxn + 1\)

那么问题就简单了,我们从 \([0,1]\) 逐步考究一段区间

对于 \([0,1]\) 我们可以得到 \([l,r]\) 中有多少个 \(1\),累加到域中,得到 \([0,1] \rightarrow [0,ans_1]\)

然后,对于 \([0,ans_1]\),由于我们已经将 \([0,1]\) 中的所有数字累加到域中了,那么我们自然从域 \([2,ans_1]\) 中累加数字,得到 \([0,ans_1]\rightarrow[0,ans_2]\)

然后,对于 \([0,ans_2]\),由于我们已经将 \([0,ans_1]\) 的数字全部累加完了,我们只需要累加 \([ans_1 + 1]\) 中的数字,得到 \([0,ans_2] \rightarrow [0,ans_3]\)

以此类推,就得到:

在添加完 \([0,r]\)\([0,l-1]\) 两个时间戳内的数字

\[ 对于 [0,lastans]\ ,\ 将\ [l,r]\ 中[lastans + 1,ans]扩展到 [0,ans] 中,使 [0,ans] \rightarrow [0,newans] \\ 其中 [0,lastans] 是上一次已经累加过的区域,[lastans + 1,ans]是询问时未累加的区域,[0,newans] 是新的扩展域 \]

直到无法继续累加为止,输出 \(ans\),即可

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e5+5,inf = 1e9;

int rt[N<<2],cnt;

namespace sgt{
struct seg{
	int v,ls,rs;
}t[N<<8];

#define v(p) t[(p)].v
#define ls(p) t[(p)].ls
#define rs(p) t[(p)].rs
#define mid ((pl + pr) >> 1)

void push_up(int p) {
	v(p) = v(ls(p)) + v(rs(p));
}

void update(int r,int &p,int pl,int pr,int q,int V){
	if(!p) p = ++cnt;
	if(pl == pr) {
		v(p) += V;
		return;
	}
	if(q <= mid) {
		rs(p) = rs(r);
		ls(p) = ++cnt;
		t[ls(p)] = t[ls(r)];
		update(ls(r),ls(p),pl,mid,q,V); 
	}else {
		ls(p) = ls(r);
		rs(p) = ++cnt;
		t[rs(p)] = t[rs(r)];
		update(rs(r),rs(p),mid+1,pr,q,V);
	}
	push_up(p);
}

int query(int r,int p,int pl,int pr,int ql,int qr){
	if(ql <= pl && pr <= qr) return v(p) - v(r);
	int res = 0;
	if(ql <= mid) res += query(ls(r),ls(p),pl,mid,ql,qr);
	if(qr > mid) res += query(rs(r),rs(p),mid+1,pr,ql,qr);
	return res;
}

}

int n,m;

signed main() {
	n = rd();
	for(int i = 1;i<=n;i++) {
		int t = rd();
		sgt::update(rt[i-1],rt[i],1,inf,t,t);
	}
	m = rd();
	while(m--) {
		int ans = 1,l = rd(),r = rd(),lst = 0;
		while(1) {
			int res = sgt::query(rt[l - 1],rt[r],1,inf,lst + 1,ans);
			lst = ans;
			if(res) ans = res + ans;
			else break;
		}
		wt(ans),putchar('\n');
	}

	return 0;
}

P3380 树套树#

有人没有写动态开点,我不说是谁

空间爆炸的代码
DEAD-code:

#include<bits/stdc++.h>
using namespace std;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int M = 5e4+5;

int n,m,a[M];

struct FHQ{
int cnt = 0,root = 0;

struct node{
    int ls,rs;
    int key,pri;
    int size;
};

#define ls(p) t[p].ls
#define rs(p) t[p].rs
#define pri(p) t[p].pri


void newnode(int x) {
    cnt++;
    t[cnt].size = 1;
    t[cnt].ls = t[cnt].rs = 0;
    t[cnt].key = x;
    t[cnt].pri = rand();
}

void update(int u) {
  t[u].size = t[ls(u)].size + t[rs(u)].size + 1;
}

void spilt(int u,int x,int &L,int &R) {
  if(u == 0) {L = R = 0;return;}
  if(t[u].key <= x) {
    L = u;
    spilt(rs(u),x,rs(u),R);
  }else  {
    R = u;
    spilt(ls(u),x,L,ls(u));
  }
  update(u);
}

int merge(int L,int R) {
  if(L == 0 || R == 0 ) return L + R;
  if(pri(L) > pri(R)) {
    rs(L) = merge(rs(L),R);
    update(L);
    return L;
  }else {
    ls(R) = merge(L,ls(R));
    update(R);
    return R;
  }
}

void insert(int x) {
  int L,R;
  spilt(root,x,L,R);
  newnode(x);
  int aa = merge(L,cnt);
  root = merge(aa,R);
}

void del(int x){
  int L,R,p;
  spilt(root,x,L,R);
  spilt(L,x-1,L,p);
  p = merge(ls(p),rs(p));
  root = merge(merge(L,p),R);
}

int rank(int x){
    int L,R;
    spilt(root,x-1,L,R);
    int rv = t[L].size + 1;
    root = merge(L,R);
    return rv;
}

int kth(int u,int k) {
  if(k == t[ls(u)].size + 1) return u;
  if(k <= t[ls(u)].size) return kth(ls(u),k);
  if(k > t[ls(u)].size) return kth(rs(u),k - t[ls(u)].size - 1);
}

int pre(int x) {
    int L,R;
    spilt(root,x-1,L,R);
    int rv = t[kth(L,t[L].size)].key;
    root = merge(L,R);
    return rv;
}

int suc(int x) {
  int L,R;
  spilt(root,x,L,R);
  int rv = t[kth(R,1)].key;
  root = merge(L,R);
  return rv;
}

#undef ls
#undef rs
#undef mid
};

const int inf = 2147483647;

namespace sgt{
FHQ t[M<<2];

#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

void build(int p,int pl,int pr){
	for(int i = pl;i<=pr;i++) t[p].insert(a[pl]);
	t[p].insert(inf),t[p].insert(-inf);
	if(pl == pr) return;
	build(ls,pl,mid);
	build(rs,mid+1,pr);
}	

void update(int p,int pl,int pr,int k,int v){
	t[p].del(a[k]);
	t[p].insert(v);
	if(pl == pr) return;
	if(k <= mid) update(ls,pl,mid,k,v);
	else update(rs,mid+1,pr,k,v);
}

int getrank(int p,int pl,int pr,int l,int r,int k){
	if(l <= pl && pr <= r) return t[p].rank(k);
	if(r <= mid) {
		return getrank(ls,pl,mid,l,r,k);
	} else if(l > mid) {
		return getrank(rs,mid+1,pr,l,r,k);
	}else {
		int m = getrank(ls,pl,mid,l,r,k),n = getrank(rs,mid+1,pr,l,r,k);
		return m + n;
	}
}

int getkth(int pl,int pr,int k) {
	int l = 0,r = 1e8;
	while(l < r) {
		int Mid = (l + r + 1) / 2;
		if(getrank(1,1,n,pl,pr,Mid) < k)
        l = Mid;
    else r = Mid - 1;
	}
  return r;
}

int getpre(int p,int pl,int pr,int l,int r,int k){
  if(l <= pl && pr <= r) return t[p].pre(k);
  if(r <= mid) return getpre(ls,pl,mid,l,r,k);
  else if(l > mid) return getpre(rs,mid+1,pr,l,r,k);
  else {
    int m = getpre(ls,pl,mid,l,r,k),n = getpre(rs,mid+1,pr,l,r,k);
    return min(m,n);
  }
}

int getsuc(int p,int pl,int pr,int l,int r,int k) {
  if(l <= pl && pr <= r) return t[p].suc(k);
  if(r <= mid) return getsuc(ls,pl,mid,l,r,k);
  else if(l > mid) return getsuc(rs,mid+1,pr,l,r,k);
  else {
    int m = getsuc(ls,pl,mid,l,r,k),n = getsuc(rs,mid+1,pr,l,r,k);
    return max(m,n);
  }
}

#undef ls
#undef rs 
#undef mid
}


signed main() {

	n = rd(),m = rd();
	for(int i = 1;i<=n;i++) a[i] = rd();
  sgt::build(1,1,n);

  while(m--) {
    int opt = rd();
    int l = rd(),r = rd(),k = rd();
    switch (opt)
    {
    case 1:
      wt(sgt::getrank(1,1,n,l,r,k));
	  putchar('\n');
      break;
    case 2:
      wt(sgt::getkth(l,r,k));
	  putchar('\n');
      break;
    case 3:
      sgt::update(1,1,n,l,r);
      break;
    case 4:
      wt(sgt::getpre(1,1,n,l,r,k));
	  putchar('\n');
      break;
    case 5:
      wt(sgt::getsuc(1,1,n,l,r,k));
	  putchar('\n');
      break;
    }
  }

	return 0;
}

这下不得不练动态开点线段树了

template:

struct segment_tree
{
    int sum,lson,rson;
}tree[maxq<<2];
int tot;
void update(int &pos,int l,int r,int k)
{
    int mid=(l+r)>>1;
    if(!pos)
        pos=++tot;
   	if(l==r)
    {
        tree[pos].sum+=k;
        return;
    }
    if(x<=mid)
        update(tree[pos].lson,l,mid,k);
    if(y>mid)
        update(tree[pos].rson,mid+1,r,k);
    tree[pos].sum=tree[tree[pos].lson].sum+tree[tree[pos].rson].sum;
}
int main()
{
    int root=0;
    update(root,1,maxn,k);
    return 0;
}

树套树今天就先鸽着 (写了一下午)(难绷

动态开点线段树#

P5459 [BJOI2016] 回转寿司#

动态开点练手题

新手村的题目属于是

题面:

题目描述

酷爱日料的小Z经常光顾学校东门外的回转寿司店。在这里,一盘盘寿司通过传送带依次呈现在小Z眼前。

不同的寿司带给小Z的味觉感受是不一样的,我们定义小Z对每盘寿司都有一个满意度。

例如小Z酷爱三文鱼,他对一盘三文鱼寿司的满意度为 \(10\);小Z觉得金枪鱼没有什么味道,他对一盘金枪鱼寿司的满意度只有 \(5\);小Z最近看了电影《美人鱼》,被里面的八爪鱼恶心到了,所以他对一盘八爪鱼刺身的满意度是 \(-100\)

特别地,小Z是个著名的吃货,他吃回转寿司有一个习惯,我们称之为“狂吃不止”。具体地讲,当他吃掉传送带上的一盘寿司后,他会毫不犹豫地吃掉它后面的寿司,直到他不想再吃寿司了为止。

今天,小Z再次来到了这家回转寿司店,\(N\) 盘寿司将依次经过他的面前。其中,小Z对第 \(i\) 盘寿司的满意度为\(a_i\)

小Z可以选择从哪盘寿司开始吃,也可以选择吃到哪盘寿司为止。他想知道共有多少种不同的选择,使得他的满意度之和不低于 \(L\),且不高于 \(R\)

注意,虽然这是回转寿司,但是我们不认为这是一个环上的问题,而是一条线上的问题。即,小Z能吃到的是输入序列的一个连续子序列;最后一盘转走之后,第一盘并不会再出现一次。

输入格式

第一行三个正整数 \(N,L,R\),表示寿司盘数,满意度的下限和上限。
第二行包含 \(N\) 个整数 \(a_i\),表示小Z对寿司的满意度。

输出格式

一行一个整数,表示有多少种方案可以使得小Z的满意度之和不低于 \(L\) 且不高于 \(R\)

样例 #1
样例输入 #1
5 5 9
1 2 3 4 5
样例输出 #1
6
提示

【数据范围】

\(1\le N \le 10^5\)
\(|a_i| \le 10^5\)
\(0\le L,R \le 10^9\)

形式化的:
我们需要在序列中找出所有满足

\[ 对于 i < j ,\sum_{k = i}^j a_k \in [L,R] \]

的子序列 \([i,j]\) 的个数

看到这题,自然会想到 逆序对统计、加入循环的思路

本题也是一样

既然题目中有 \(\sum\),那我们就先用前缀和先处理出来

然后,插入一个前缀和,找 \(k - R \sim k - L\) 中的个数

统计出来就结束了

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e5+5,inf = 1e18;

int n,l,r,a[N];

namespace sgt{
#define mid ((pl + pr) >> 1)
int t[N * 34 * 4 + 5],cnt,ls[N * 34 * 4 + 5],rs[N * 34 * 4 + 5];
int root = 0;
void push_up(int p) {
	t[p] = t[ls[p]] + t[rs[p]];
}

void update(int &p,int k,int d,int pl = -inf,int pr = inf) {
	if(!p) p = ++cnt;
	if(pl == pr) {t[p] += d;return;}
	if(k <= mid) update(ls[p],k,d,pl,mid);
	else update(rs[p],k,d,mid+1,pr);
	push_up(p);
}

int query(int &p,int l,int r,int pl = -inf,int pr = inf) {
	if(!p) return 0;
	if(l <= pl && pr <= r) return t[p];
	int res = 0;
	if(l <= mid) res += query(ls[p],l,r,pl,mid);
	if(r > mid) res += query(rs[p],l,r,mid+1,pr);
	return res;
}

#undef ls 
#undef rs 
#undef mid 
}

int ans = 0;

signed main() {

	n = rd(),l = rd(),r = rd();
	for(int i = 1;i<=n;i++) a[i] = rd();
	for(int i = 1;i<=n;i++) a[i] = a[i-1] + a[i];

	sgt::update(sgt::root,a[0],1);
	
	for(int i = 1;i<=n;i++) {
		ans += sgt::query(sgt::root,a[i] - r,a[i] - l);
		sgt::update(sgt::root,a[i],1);
	}

	wt(ans);
	putchar('\n');
	return 0;
}

P8818 [CSP-S 2022] 策略游戏#

题面:

题目描述

小 L 和小 Q 在玩一个策略游戏。

有一个长度为 \(n\) 的数组 \(A\) 和一个长度为 \(m\) 的数组 \(B\),在此基础上定义一个大小为 \(n \times m\) 的矩阵 \(C\),满足 \(C_{i j} = A_i \times B_j\)。所有>下标均从 \(1\) 开始。

游戏一共会进行 \(q\) 轮,在每一轮游戏中,会事先给出 \(4\) 个参数 \(l_1, r_1, l_2, r_2\),满足 \(1 \le l_1 \le r_1 \le n\)\(1 \le l_2 \le r_2 \le m\)

游戏中,小 L 先选择一个 \(l_1 \sim r_1\) 之间的下标 \(x\),然后小 Q 选择一个 \(l_2 \sim r_2\) 之间的下标 \(y\)。定义这一轮游戏中二人的得分是 \(C_{x y}\)

小 L 的目标是使得这个得分尽可能大,小 Q 的目标是使得这个得分尽可能小。同时两人都是足够聪明的玩家,每次都会采用最优的策略。

请问:按照二人的最优策略,每轮游戏的得分分别是多少?

输入格式

第一行输入三个正整数 \(n, m, q\),分别表示数组 \(A\),数组 \(B\) 的长度和游戏轮数。

第二行:\(n\) 个整数,表示 \(A_i\),分别表示数组 \(A\) 的元素。

第三行:\(m\) 个整数,表示 \(B_i\),分别表示数组 \(B\) 的元素。

接下来 \(q\) 行,每行四个正整数,表示这一次游戏的 \(l_1, r_1, l_2, r_2\)

输出格式

输出共 \(q\) 行,每行一个整数,分别表示每一轮游戏中,小 L 和小 Q 在最优策略下的得分。

样例 #1
样例输入 #1
3 2 2
0 1 -2
-3 4
1 3 1 2
2 3 2 2
样例输出 #1
0
4
样例 #2
样例输入 #2
6 4 5
3 -1 -2 1 2 0
1 2 -1 -3
1 6 1 4
1 5 1 4
1 4 1 2
2 6 3 4
2 5 2 3
样例输出 #2
0
-2
3
2
-1
提示

【样例解释 #1】

这组数据中,矩阵 \(C\) 如下:

\[\begin{bmatrix} 0 & 0 \\ -3 & 4 \\ 6 & -8 \end{bmatrix} \]

在第一轮游戏中,无论小 L 选取的是 \(x = 2\) 还是 \(x = 3\),小 Q 都有办法选择某个 \(y\) 使得最终的得分为负数。因此小 L 选择 \(x = 1\) 是最优的,因为这样得>分一定为 \(0\)

而在第二轮游戏中,由于小 L 可以选 \(x = 2\),小 Q 只能选 \(y = 2\),如此得分为 \(4\)

【样例 #3】

见附件中的 game/game3.ingame/game3.ans

【样例 #4】

见附件中的 game/game4.ingame/game4.ans

【数据范围】

对于所有数据,\(1 \le n, m, q \le {10}^5\)\(-{10}^9 \le A_i, B_i \le {10}^9\)。对于每轮游戏而言,\(1 \le l_1 \le r_1 \le n\)\(1 \le l_2 \le r_2 \le m\)

测试点编号 \(n, m, q \le\) 特殊条件
\(1\) \(200\) 1, 2
\(2\) \(200\) 1
\(3\) \(200\) 2
\(4 \sim 5\) \(200\)
\(6\) \(1000\) 1, 2
\(7 \sim 8\) \(1000\) 1
\(9 \sim 10\) \(1000\) 2
\(11 \sim 12\) \(1000\)
\(13\) \({10}^5\) 1, 2
\(14 \sim 15\) \({10}^5\) 1
\(16 \sim 17\) \({10}^5\) 2
\(18 \sim 20\) \({10}^5\)

其中,特殊性质 1 为:保证 \(A_i, B_i > 0\)
特殊性质 2 为:保证对于每轮游戏而言,要么 \(l_1 = r_1\),要么 \(l_2 = r_2\)

鉴定为 大分讨

我们直接来分析局面

上图!

首先来分析简单的

我们假设 \(L\) 先手,\(Q\) 后手

情况 \(\textcircled{1}\)

img

\(L,Q\) 都在 \(0\) 左侧

\(L\) 肯定选区间最大值,\(Q\) 肯定选区间最小值

情况 \(\textcircled{2}\)

img

\(L,Q\) 都在 \(0\) 右侧

\(L\) 肯定选区间最小值,\(Q\) 肯定选区间最大值

情况 \(\textcircled{3}\)

img

\(L\) 肯定选区间最小值,\(Q\) 肯定选区间最小值

情况 \(\textcircled{4}\)

img

\(L\) 肯定选区间最小值,\(Q\) 肯定选区间最大值

再然后就比较难想了

情况 \(\textcircled{5}\)

img

\(L\) 肯定选区间最大值,\(Q\) 肯定选区间最小值

情况 \(\textcircled{6}\)

img

\(L\) 肯定选区间最小值,\(Q\) 肯定选区间最大值

最困难的

情况 \(\textcircled{7} \rightarrow 一般情况\)

img

作为 \(L\),显然取最大值、最小值,都会被反杀

那么,我们就让损失最小化

容易想到,我们取 \(L,Q\) 靠近 \(0\) 两侧的值

如图:

img

这样,损失最小

那么我们要特别处理 \(L\) 序列的非正最大值,非负最小值---我们在线段树里额外维护一下就可以了

此题完美结束!

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int M = 1e5+5,inf = 2139062143;

int n,m,q,a[M];

struct sgt{
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((pl + pr) >> 1)

int mn[M<<2],mx[M<<2];
int _mn[M<<2],_mx[M<<2];

void push_up(int p) {
	mn[p] = min(mn[ls],mn[rs]);
	mx[p] = max(mx[ls],mx[rs]);
	_mn[p] = min(_mn[ls],_mn[rs]);
	_mx[p] = max(_mx[ls],_mx[rs]);
}

void build(int p,int pl,int pr){
	if(pl == pr) {
		mn[p] = mx[p] = a[pl];
		_mn[p] = a[pl] >= 0 ? a[pl] : inf;
		_mx[p] = a[pl] <= 0 ? a[pl] : -inf;
		return;
	}
	build(ls,pl,mid);
	build(rs,mid+1,pr);
	push_up(p);
}

int querymax(int p,int pl,int pr,int l,int r) {
	if(l <= pl && pr <= r) return mx[p];
	int res = -inf;
	if(l <= mid) res = max(res,querymax(ls,pl,mid,l,r));
	if(r > mid) res = max(res,querymax(rs,mid+1,pr,l,r));
	return res;
}

int query_max(int p,int pl,int pr,int l,int r) {
	if(l <= pl && pr <= r) return _mx[p];
	int res = -inf;
	if(l <= mid) res = max(res,query_max(ls,pl,mid,l,r));
	if(r > mid) res = max(res,query_max(rs,mid+1,pr,l,r));
	return res;
}

int query_min(int p,int pl,int pr,int l,int r){
	if(l <= pl && pr <= r) return _mn[p];
	int res = inf;
	if(l <= mid) res = min(res,query_min(ls,pl,mid,l,r));
	if(r > mid) res = min(res,query_min(rs,mid+1,pr,l,r));
	return res;
}

int querymin(int p,int pl,int pr,int l,int r) {
	if(l <= pl && pr <= r) return mn[p];
	int res = inf;
	if(l <= mid) res = min(res,querymin(ls,pl,mid,l,r));
	if(r > mid) res = min(res,querymin(rs,mid+1,pr,l,r));
	return res;
}

}c,d;

signed main() {	
	n = rd(),m = rd(),q = rd();
	for(int i = 1;i<=n;i++) a[i] = rd();
	c.build(1,1,n);
	for(int i = 1;i<=m;i++) a[i] = rd();
	d.build(1,1,m);

	while(q--) {
		int l1 = rd(),r1 = rd(),l2 = rd(),r2 = rd();
		int mna = c.querymin(1,1,n,l1,r1),mnb = d.querymin(1,1,m,l2,r2);
		int mxa = c.querymax(1,1,n,l1,r1),mxb = d.querymax(1,1,m,l2,r2);
		if(mna >= 0 && mnb >= 0) wt(mxa * mnb);
		else if(mxa <= 0 && mxb <= 0) wt(mna * mxb);
		else if(mna >= 0 && mnb <= 0) wt(mna * mnb);
		else if(mxa <= 0 && mxb >= 0) wt(mxb * mxa);
		else if(mna <= 0 && mxa >= 0 && mnb >= 0) wt(mxa * mnb);
		else if(mxb <= 0 && mxa >= 0 && mna <= 0) wt(mna * mxb); 
		else{
			int ra = c.query_min(1,1,n,l1,r1),rb = d.querymin(1,1,m,l2,r2);
			int ora = c.query_max(1,1,n,l1,r1),orb = d.querymax(1,1,m,l2,r2);
			int res = max(ra * rb,ora * orb);
			wt(res);
		}  
		putchar('\n');
	}


	return 0;
}

2024/07/09#

校内模拟赛 #1#

Codeforces Round 936 (Div. 2) -> E. Girl Permutation#

题面:

某个长度为 \(n\) 的排列需要被猜中。

您将得到其前缀最大值和后缀最大值的索引。

长度为 \(k\) 的排列是一个大小为 \(k\) 的数组,从 \(1\)\(k\) 的每个整数都正好出现一次。

前缀最大值是指在以该元素结尾的前缀上最大的元素。更正式地说,如果每个 \(j\) < \(i\)\(a_i\) > \(a_j\) 都是前缀最大值,那么元素 \(a_i\) 就是前缀最大值。

同样,后缀最大值的定义是:如果每个 \(j\) > \(i\)\(a_i\) > \(a_j\) 都是后缀最大值,那么元素 \(a_i\) 就是后缀最大值。

您需要输出可能被猜中的不同排列组合的数量。

由于这个数字可能非常大,请输出以 \(10^9 + 7\) 为模数的答案。

首先,观察题目,不难看出指定位置(前、后缀最大值位置)形成一个 ,如图:

img

然后,我们不急着去想排列组合

先否定掉最错误的答案———— \(p_1\) 显然是 \(1\)\(p_{m_1}\) 显然和 \(s_1\) 相等形成 \(s_{m_2}\) 显然是 \(n\)

那么对于不满足上述要求的一律去掉

接下来,我们就可以根据最大值两侧分开分析

我们先看最大值左侧

下文中,\(ans\) 初值为 \(1\)

当我们确定最大值时,左侧 \(n - 1\) 个数可以被选,有 \(p_{m_1} - 1\) 个位置,那么 \(ans \leftarrow ans \cdot \dbinom{n - 1}{p_{m_1} - 1}\)

再将 \(p_{m_1 - 1}\) 位置为界,将左侧又分成 \(1 \sim (p_{m_1-1} - 1)\)\((p_{m_1 - 1} + 1) \sim (p_{m_1} - 1)\) 两部分

所以 \(ans \leftarrow ans \cdot \dbinom{p_{m_1} - 2}{p_{m_1} - 1}\)

而且 \((p_{m_1 - 1} + 1) \sim (p_{m_1} - 1)\) 这部分的值可以进行排列

又有 \(ans \leftarrow ans \cdot (p_{m_1} - p_{m_1 - 1} + 1)!\)

以此类推,直到分到 \(p_1\) 结束

对于 \(s\),自然同理即可

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int mod = 1e9+7;

const int N = 2e5+5;

int fac[N],inv[N];

int qpow(int x,int k) {
	int res = 1;
	while(k) {
		if(k & 1) res = (res * x) % mod;
		x = (x * x) % mod;
		k >>= 1;
	}
	return res;
}

int dbinom(int x,int y) {
	if(x < y) return 0;
	return fac[x] * inv[y] % mod * inv[x - y] % mod;
}

void solve() {
	int n = rd(),m1 = rd(),m2 = rd();

	vector<int> p(m1 + 1),s(m2 + 1);
	for(int i = 1;i<=m1;i++) p[i] = rd();
	for(int i = 1;i<=m2;i++) s[i] = rd();
	if(p[1] != 1 || p[m1] != s[1] || s[m2] != n) {puts("0");return;}

	int res = dbinom(n - 1,p[m1] - 1);
	for(int i = m1 - 1;i >= 1;i--) {
		res = (res * dbinom(p[i + 1] - 2,p[i] - 1)) % mod;
		res = (res * fac[p[i + 1] - p[i] - 1]) % mod;
	}
	for(int i = 2;i<=m2;i++) {
		res = (res * dbinom(n - s[i - 1] - 1,n - s[i])) % mod;
		res = (res * fac[(s[i] - s[i-1] - 1)]) % mod;
	}
	wt(res),putchar('\n');
}

signed main() {
	fac[0] = fac[1] = 1;
	for(int i = 2;i<=2e5;i++) fac[i] = (fac[i-1] * i) % mod;
	inv[0] = inv[1] = 1;
	for(int i = 2;i<=2e5;i++) inv[i] = (mod - mod / i) * inv[mod%i] % mod;
	for(int i = 2;i<=2e5;i++) inv[i] = inv[i-1] * inv[i] % mod;

	int t = rd();
	while(t--) solve();	

	return 0;
}

P3924 康娜的线段树#

题面:

题目描述

小林是个程序媛,不可避免地康娜对这种人类的“魔法”产生了浓厚的兴趣,于是小林开始教她OI。

今天康娜学习了一种叫做线段树的神奇魔法,这种魔法可以维护一段区间的信息,是非常厉害的东西。康娜试着写了一棵维护区间和的线段树。由于她不会打标记,因此所有的区间加操作她都是暴力修改的。具体的代码如下:

struct Segment_Tree{
#define lson (o<<1)
#define rson (o<<1|1)
   int sumv[N<<2],minv[N<<2];
   inline void pushup(int o){sumv[o]=sumv[lson]+sumv[rson];}
   inline void build(int o,int l,int r){
       if(l==r){sumv[o]=a[l];return;}
       int mid=(l+r)>>1;
       build(lson,l,mid);build(rson,mid+1,r);
       pushup(o);
   }
   inline void change(int o,int l,int r,int q,int v){
       if(l==r){sumv[o]+=v;return;}
       int mid=(l+r)>>1;
       if(q<=mid)change(lson,l,mid,q,v);
       else change(rson,mid+1,r,q,v);
       pushup(o);
   }
}T; 

在修改时,她会这么写:

for(int i=l;i<=r;i++)T.change(1,1,n,i,addv);

显然,这棵线段树每个节点有一个值,为该节点管辖区间的区间和。

康娜是个爱思考的孩子,于是她突然想到了一个问题:

如果每次在线段树区间加操作做完后,从根节点开始等概率的选择一个子节点进入,直到进入叶子结点为止,将一路经过的节点权值累加,最后能得到的期望值是多少?

康娜每次会给你一个值 \(qwq\) ,保证你求出的概率乘上 \(qwq\) 是一个整数。

这个问题太简单了,以至于聪明的康娜一下子就秒了。

现在她想问问你,您会不会做这个题呢?

输入格式

第一行整数 \(n,m,qwq\) 表示线段树维护的原序列的长度,询问次数,分母。

第二行 \(n\) 个数,表示原序列。

接下来 \(m\) 行,每行三个数 \(l,r,x\) 表示对区间\([l,r]\) 加上 \(x\)

输出格式

\(m\) 行,表示期望的权值和乘上qwq结果。

样例 #1
样例输入 #1
8 2 1
1 2 3 4 5 6 7 8
1 3 4
1 8 2
样例输出 #1
90
120
提示

对于30%的数据,保证 \(1 \leq n,m \leq 100\)

对于70%的数据,保证 \(1 \leq n,m, \leq 10^{5}\)

对于100%的数据,保证$1 \leq n,m \leq 10^6 $

\(-1000 \leq a_i,x \leq 1000\)

这个线段树成功让我在赛时卡了足足 \(5h\) 还只拿了 \(50\)

但是,这道题让我找到了造新题的思路

先上场的是我的难绷思路:(大家可以直接跳转到 正解

我们可以注意到

根据 期望定义,$ 期望 = (基本事件值总和) / 基本事件数 $

基本事件数不变,那么每次询问只改变 基本事件总和

所以初始数组,每一次询问,都是可以分开分析的

面对这样一个线段树,如图:

img

因为线段树只有最后一层不满,因此,我们可以发现

\[P(每个以最后一层叶子节点为终点的路径的概率) = \frac{1}{2}P(每个以倒数第 2 层的叶子节点为终点的路径概率) \]

我们就让 \(前者 = 1\)\(后者 = 2\),建一颗辅助线段树去查询每个节点被访问的次数

img

然后再 \(build\) 一颗正常线段树,让 \(a 数组的贡献 \rightarrow ans\)

初始数组搞定,然后是每一次询问


当我们模拟过程时,将每一次累加值都拆开

就会发现:
   令 \(v = 询问值\ \ ,\ \ len = 询问覆盖的区间长度\ \ ,\ \ t = 访问总次数\)

   那么,对于任意一个节点,贡献为 \(v \cdot len \cdot t\)

   又因为对于每一次询问,\(v\) 始终是定值,

   那么,对于一段询问区间,有:

\[贡献总和 = \sum_{i \in 涵盖l,r的节点} vlen_it_i =\ v\sum_{i \in 被l,r覆盖的节点}len_it_i \]


那么,用一颗树剖树去维护这个 '\(\sum\)',难绷的思路就结束了

DEAD-code:

#include<bits/stdc++.h>
using namespace std;

#define int __int128

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e6+5;
bool Mbe;
int n,m,q,a[N];

int nxt[N<<1],to[N<<1],cnt,head[N];

void init() {
	memset(head,-1,sizeof(head));
}

void add(int u,int v) {
	nxt[cnt] = head[u];
	to[cnt] = v;
	head[u] = cnt++;
}

int qt[N<<2];

namespace Segtree_2{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

void push_up(int p) {
	qt[p] = qt[ls] + qt[rs];
}

void build(int p,int pl,int pr) {
	if(pl == pr) {qt[p]++;return;}
	build(ls,pl,mid);
	if(mid + 1 == pr && pl != mid) qt[rs]++;
	build(rs,mid+1,pr);
	push_up(p);
}

void build_QTree(int p,int pl,int pr) {
	int f = (p >> 1);
	qt[p] *= (pr - pl + 1);
	add(f,p);
	add(p,f);
	if(pl == pr) return;
	build_QTree(ls,pl,mid);
	build_QTree(rs,mid+1,pr);
}

#undef ls
#undef rs
#undef mid
}

int fa[N<<2],top[N<<2],dep[N<<2],son[N<<2],siz[N<<2],id[N<<2];

void dfs1(int x,int f){
	fa[x] = f;
	siz[x] = 1;
	dep[x] = dep[f] + 1;
	for(int i = head[x];~i;i = nxt[i]) {
		int y = to[i];
		if(y ^ f) {
			dfs1(y,x);
			siz[x] += siz[y];
			if(siz[son[x]] < siz[y]) son[x] = y;
		}
	}
}

int num,w_new[N<<2];

void dfs2(int x,int topx){
	top[x] = topx;
	id[x] = ++num;
	w_new[num] = qt[x];
	if(!son[x]) return;
	dfs2(son[x],topx);
	for(int i = head[x];~i;i = nxt[i]) {
		int y = to[i];
		if(y ^ fa[x] && y ^ son[x]) dfs2(y,y);
	}
}

namespace QTree{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

int t[N<<4];
void push_up(int p) {
	t[p] = t[ls] + t[rs];
}
void build(int p,int pl,int pr) {
	if(pl == pr) {
		t[p] = w_new[pl];
		return;
	}
	build(ls,pl,mid);
	build(rs,mid+1,pr);
	push_up(p);
}
int query(int p,int pl,int pr,int l,int r) {
	if(l <= pl && pr <= r) return t[p];
	int res = 0;
	if(l <= mid) res += query(ls,pl,mid,l,r);
	if(r > mid) res += query(rs,mid+1,pr,l,r);
	return res;
}

#undef ls
#undef rs
#undef mid
}

int query_tree(int x) {
	return QTree::query(1,1,num,id[x],id[x] + siz[x] - 1);
}

int ans = 0;

namespace Segtree_1{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

int t[N<<2];

void push_up_build(int p,int pl,int pr) {
	t[p] = t[ls] + t[rs];
	ans += t[p] * qt[p]/ (pr - pl + 1);
}

void build(int p,int pl,int pr) {
	if(pl == pr) {
		t[p] = a[pl];
		ans += t[p] * qt[p];
		return;
	}
	build(ls,pl,mid);
	build(rs,mid+1,pr);
	push_up_build(p,pl,pr);
}

#undef ls
#undef rs
#undef mid
}

#define ls (p << 1)
#define rs (ls | 1)
int query_range(int p,int pl,int pr,int l,int r,int v) {
	if(l <= pl && pr <= r) return query_tree(p) * v;
	int res = qt[p] / (pr - pl + 1) * v * (min(r,pr) - max(l,pl) + 1),mid = (pl + pr) >> 1;
	if(l <= mid) res += query_range(ls,pl,mid,l,r,v);
	if(r > mid) res += query_range(rs,mid+1,pr,l,r,v);
	return res;
}
#undef ls
#undef rs

bool Med;

signed main() {
// 	freopen("P3924_1.in","r",stdin);
// 	freopen("ans.out","w",stdout);

	fprintf(stderr, "%.3lf MB\n", (&Med - &Mbe) / 1048576.0);
	init();
	n = rd(),m = rd(),q = rd();
	for(int i = 1;i<=n;i++) a[i] = rd();

	Segtree_2::build(1,1,n);
	Segtree_2::build_QTree(1,1,n);
	Segtree_1::build(1,1,n);
	dfs1(1,0);dfs2(1,1);
	QTree::build(1,1,num);
	
	while(m--) {
		int l = rd(),r = rd(),x = rd();
		ans += query_range(1,1,n,l,r,x);
		wt(ans * q * n/ qt[1]);
		putchar('\n');
	}

	return 0;
}

记录:

img

正解

今天太累了,待补

在此感谢ckain大佬的讲解

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N = 1e6+5;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

int a[N],n,m,qwq,s[N];

int t[N<<2],dep[N<<2],maxd;

#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((pl + pr ) >> 1)

void build(int p,int pl,int pr,int d) {
	if(pl == pr) {
		dep[pl] = d;
		maxd = max(d,maxd);
		t[p] = a[pl];
		return;
	}
	build(ls,pl,mid,d + 1);
	build(rs,mid+1,pr,d + 1);
	t[p] = t[ls] + t[rs];
}

int query(int p,int pl,int pr,int T,int tt) {
	if(pl == pr) return (1 << T) * (tt + t[p]);
	return query(ls,pl,mid,T - 1,tt + t[p]) + query(rs,mid+1,pr,T - 1,tt + t[p]);
}

#undef ls 
#undef rs
#undef mid 

signed main() {

	n = rd(),m = rd(),qwq = rd();
	for(int i = 1;i<=n;i++) a[i] = rd();
	build(1,1,n,1);
	int ans = query(1,1,n,maxd - 1,0);
	int y = (1<<(maxd - 1));
	int gcd = __gcd(y,qwq);
	y /= gcd;
	qwq /= gcd;
	for(int i = 1;i<=n;i++) s[i] = s[i-1] + (((1 <<(dep[i])) - 1) << (maxd - dep[i]));
	while(m--) {
		int l = rd(),r = rd(),x = rd();
		ans += (s[r] - s[l - 1]) * x;
		wt(ans / y * qwq),putchar('\n');
	}

	return 0;
}

P8352 [SDOI/SXOI2022] 小 N 的独立集#

题面:

[SDOI/SXOI2022] 小 N 的独立集
题目描述

小 N 喜欢出最大权独立集问题。

有一天,他接到了一系列的出题任务,于是他顺手出了一系列最大权独立集问题。

为了同时给这一系列题目造数据,小 N 生成了一个 \(n\) 个点的树,并选定了一个正整数 \(k\)。这样每生成一组数据时,他只需要对于每个点,随机生成一个在 \(1 \sim k\) 之间的整数点权,就可>以生成一个新的最大独立集问题。

小 N 把这些题给了他的好朋友,小 Ω。小 Ω 表示,这些题太多太乱了,他打算把所有的 \(k^n\) 道题归类处理。一个自然的想法就是按答案(也就是最大权独立集中的点的权值之和)分类,显然这>些最大权独立集问题的答案一定在 \(1 \sim nk\) 之间,所以小 Ω 只需要将所有题目按照答案分成 \(nk\) 类进行管理就行了。

在小 N 正式开始出题之前,小 Ω 先要算出每一类题目具体有多少道。稍加估计之后小 Ω 很快意识到自己并没有《诗云》中描述的那种存储器,于是断然拒绝了小 N 关于“先把所有可能的题目造好>再慢慢分类统计数量”的建议,然后悲剧地意识到自己并不会计算这些数字。

他想叫你帮他解决这个问题,还说如果你成功解决了这个问题,那么在小 N 出那些最大权独立集问题的时候,他会帮你提交一份标程代码。

输入格式

第一行, \(2\) 个正整数 \(n\), \(k\)

接下来 \(n-1\) 行,每行 \(2\) 个正整数 \(u_i\), \(v_i\),描述一条连接点 \(u_i\)\(v_i\) 的边,保证这些边构成一棵树。

输出格式

\(nk\) 行,每行一个整数,第 \(i\) 个整数表示在所有可能的题目中,最大权独立集大小为 \(i\) 的有多少道,答案对 \(10^9+7\) 取模。

样例 #1
样例输入 #1
4 2
1 2
2 3
2 4
样例输出 #1
0
0
2
6
6
2
0
0
提示

【样例解释】

符合题意的最大权独立集题目一共有 \(2^4=16\) 道。

可以证明,当点 \(1\), \(3\), \(4\) 的权值均为 \(1\) 时,最大权独立集为 \(3\) ,这样的题目共有 \(2\) 道;点 \(1\), \(3\), \(4\) 的权值恰有一个为 \(2\) 时,最大权独立集为 \(4\) ,这样的题目共>有 \(6\) 道;对于最大权独立集为 \(5\)\(6\) 的情况也是类似的。

【数据范围】

对于 \(15 \%\) 的数据, \(n \leq 8\)
对于 \(30 \%\) 的数据, \(n \leq 30\)
对于 \(50 \%\) 的数据, \(n \leq 100\)
对于另外 \(10 \%\) 的数据, \(k=1\)
对于另外 \(15 \%\) 的数据, \(k=2\)
对于 \(100 \%\) 的数据, \(n \leq 1000\)\(k \leq 5\)\(u_{i}, v_{i} \leq n\)

【提示】

最大权独立集问题是指:选择一个点集,使得任意两个被选择的点都没有边直接相连,并且使得所有被选择的点的点权之和最大。

此题思路非常有趣,我觉得这个思路可以叫做 放缩状态优化

首先,我们正常去思考出一个多项式复杂度算法

这不难看出是一道 \(dp\)\(dp\)

那么我们二话不说,先设内层 \(dp\)\(g_{i,0}\)\(i\) 节点不取的最大权独立集,\(g_{i,1}\)\(i\) 节点取的最大权独立集

所以,自然而然的,我们再设出 \(f_{x,i,j}\) 表示在 \(x\) 节点符合要求的方案数,其中 \(i = g_{i,0}\)\(j = g_{i,1}\)

那么,根据最大权独立集的dp转移,该转移自然就出来了:

\(v\) 节点方案数为 \(f_{y,p,q}\)\(u\) 节点方案数为 \(f_{x,i,j}\)

对于 \(v\) 作为 \(u\) 的孩子,让 \(v\) 合并至 \(u\) 中,则

\[ f^{\prime}_{x,i + max(p,q),j + p} + f_{x,i,j} \times f_{y,p,q} \rightarrow f^{\prime}_{x,i + max(p,q),j + p} \\ 其中,f^{\prime} 为 New\ F,f 为 Old\ F \]

树上背包复杂度分析,鉴定为 \(O(n^4k^4)\),无法接受

我们发现题目中还有一个信息没有用上

\(k \leq 5\)

这个 \(k\) 非常小,那么我们就要用这个支点,优化状态结构

怎么造出 \(k\) 的有利条件呢? 答案是放缩

我们设计的 \(g\) 函数条件太苛刻,状态太绝对了

如果读者有写过 P4719 【模板】"动态 DP"&动态树分治,就会想到 \(g_{i,1}\) 可以表示为 强制不选择本节点取得的最大权独立集,而 \(g_{i,0}\)不强制选择本节点取得的最大权独立集

这样,放缩就比较明显了,对于 \(g_{i,0}\)\(g_{i,1}\),中间缺什么呢?

我们知道,不强制选择肯定最优,强制不选择不一定优,

那么,容易得到强制不选择的答案一定无限趋近与不强制选择的答案,而其中 \(差值 \leq val_i\) (因为强制不选择对于强制选择来说更灵活,所以与不强制选择相差不会超过 \(val_i\)

即:

\[0 \leq g_{i,0} - g_{i,1} \leq val_i \]

又因为,\(每个节点的权值 \leq k\)

所以

\[0 \leq g_{i,0} - g_{i,1} \leq val_i \leq k \]

放缩结束!

那么,我们不用再枚举庞大的 \(dp\) 内存,只需要寻找 \(f_{i,1} + i (i = 0 \sim k)\) 的位置就可以了

此时自然得出 \(f_{u,v_1,d}\) 表示 \(u\) 子树中 \(g_{u,0},g_{u,1}\) 分别为 \(v_1+d,v1\) 时的方案数,依然枚举 \(i,j,p,q\),有转移:

\[f_{u,i+p+q,\max(i+j+p,i+p+q)-(i+p+q)} \gets f_{u,i+p+q,\max(i+j+p,i+p+q)-(i+p+q)}+ f_{u,i,j} \times f_{v,p,q} \]

tips: 为了对比,这里展示出原先的式子中变化:

\[i \rightarrow (i + j) \\ j \rightarrow j \\ p \rightarrow (p + q)\\ q \rightarrow q\\ 原式 = f^{\prime}_{x,i + max(p,q),j + p} + f_{x,i,j} \times f_{y,p,q} \rightarrow f^{\prime}_{x,i + max(p,q),j + p} \\ 其中 f 的 第3项含义变成了 f_{i,x,y} - f_{j,p,q} 的大小 \]

此题结束!

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1005,mod = 1e9+7;

int n,k;

int head[N],nxt[N<<1],to[N<<1],cnt;

void init() {memset(head,-1,sizeof(head));}

void add(int u,int v) {
	nxt[cnt] = head[u];
	to[cnt] = v;
	head[u] = cnt++;
}

int siz[N],f[N][N * 5][6],g[N * 5][6];

inline void Add(int &a,int b){a += b;if(a >= mod) a -= mod;}

void dp(int x,int fa) {
	siz[x] = 1;
	for(int i = 1;i<=k;i++) f[x][0][i] = 1;
	for(int T = head[x];~T;T = nxt[T]) {
		int y = to[T];
		if(y == fa) continue;
		dp(y,x);
		memset(g,0,sizeof(g));
		for(int i = 0;i <= k * siz[x];i++) 
			for(int j = 0;j<=k;j++) 
				if(f[x][i][j])
					for(int p = 0;p <= k * siz[y];p++) 
						for(int q = 0;q <= k;q++)
							if(f[y][p][q])
								Add(g[i + p + q][max(i + j + p,i + p + q) - (i + p + q)],f[x][i][j] * f[y][p][q] % mod);
		memcpy(f[x],g,sizeof(g));
		siz[x] += siz[y];
	}
}

signed main() {
	init();
	n = rd(),k = rd();
	for(int i = 1,u,v;i<n;i++) {
		u = rd(),v = rd();
		add(u,v);
		add(v,u);
	}

	dp(1,0);

	for(int i = 1;i<=k * n;i++) {
		int ans = 0;
		for(int d = 0;d<=min(i,k);d++) Add(ans,f[1][i - d][d]);
		wt(ans),putchar('\n'); 
	}
	return 0;
}

回顾老题,典中典之新手恶梦

P2572 [SCOI2010] 序列操作#

没什么好说的,慢慢实现就好了

关键是各功能之间的影响、关系要理清!

AC-code:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5+5;

int n,m,a[N];

namespace sgm{
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((pl + pr) >> 1)

struct node{
	int l,r,lmax,rmax,mx,sum;
	node(int kl,int kr,int lm,int rm,int kmx,int s){
		l = kl,r = kr;
		lmax = lm,rmax = rm,mx = kmx,sum = s;
	} 
	node() {}
};


int L[N<<2],R[N<<2],mx[N<<2][2],cov[N<<2],lmax[N<<2][2],rmax[N<<2][2],sum[N<<2][2],rev[N<<2];

void newnode(int p,int pos) {
	L[p] = R[p] = a[pos];
	lmax[p][a[pos]] = rmax[p][a[pos]] = 1;
	mx[p][a[pos]] = sum[p][a[pos]] = 1;
}

void push_up(int p,int pl,int pr) {
	L[p] = L[ls],R[p] = R[rs];
	for(int i = 0;i<2;i++) {
		lmax[p][i] = lmax[ls][i];
		rmax[p][i] = rmax[rs][i];
		sum[p][i] = sum[ls][i] + sum[rs][i];
		mx[p][i] = max(mx[ls][i],mx[rs][i]);
		if(R[ls] == L[rs] && L[rs] == i) {
			if(sum[ls][i] == mid - pl + 1) lmax[p][i] += lmax[rs][i];
			if(sum[rs][i] == pr - mid) rmax[p][i] += rmax[ls][i];
			mx[p][i] = max(mx[p][i],lmax[rs][i] + rmax[ls][i]);
		}
	}
}

void build(int p,int pl,int pr) {
	cov[p] = -1,rev[p] = 0;
	if(pl == pr) {newnode(p,pl);return;}
	build(ls,pl,mid);
	build(rs,mid+1,pr);
	push_up(p,pl,pr);
}

void addCov(int p,int pl,int pr,int d){
	cov[p] = d;
	rev[p] = 0;
	lmax[p][d] = rmax[p][d] = mx[p][d] = pr - pl + 1;
	L[p] = R[p] = d;
	lmax[p][d ^ 1] = rmax[p][d ^ 1] = mx[p][d ^ 1] = 0;
	sum[p][d] = pr - pl + 1;
	sum[p][d ^ 1] = 0;
}

void addRev(int p,int pl,int pr) {
	if(cov[p] != -1) cov[p] ^= 1;
	rev[p] ^= 1;
	swap(lmax[p][0],lmax[p][1]);
	swap(rmax[p][0],rmax[p][1]);
	swap(mx[p][0],mx[p][1]);
	swap(sum[p][0],sum[p][1]);
	L[p] ^= 1;
	R[p] ^= 1;
}

void push_down(int p,int pl,int pr) {
	if(rev[p]) {
		addRev(ls,pl,mid);
		addRev(rs,mid+1,pr);
		rev[p] = 0;
	}
	if(cov[p] != -1) {
		addCov(ls,pl,mid,cov[p]);
		addCov(rs,mid+1,pr,cov[p]);
		cov[p] = -1;
	}
}

void Cov(int p,int pl,int pr,int l,int r,int d){
	if(l <= pl && pr <= r) {addCov(p,pl,pr,d);return;}
	push_down(p,pl,pr);
	if(l <= mid) Cov(ls,pl,mid,l,r,d);
	if(r > mid) Cov(rs,mid+1,pr,l,r,d);
	push_up(p,pl,pr);
}

void Rev(int p,int pl,int pr,int l,int r){
	if(l <= pl && pr <= r) {addRev(p,pl,pr);return;}
	push_down(p,pl,pr);
	if(l <= mid) Rev(ls,pl,mid,l,r);
	if(r > mid) Rev(rs,mid+1,pr,l,r);
	push_up(p,pl,pr);
}

int askSum(int p,int pl,int pr,int l,int r){
	if(l <= pl && pr <= r) {return sum[p][1];}
	push_down(p,pl,pr);
	int res = 0;
	if(l <= mid) res += askSum(ls,pl,mid,l,r);
	if(r > mid) res += askSum(rs,mid+1,pr,l,r);
	return res;
}

node cn(int p,int pl,int pr,int l,int r){
	if(l <= pl && pr <= r) {return node(L[p],R[p],lmax[p][1],rmax[p][1],mx[p][1],sum[p][1]);}
	push_down(p,pl,pr);
	if(l > mid) return cn(rs,mid+1,pr,l,r);
	else if(r <= mid) return cn(ls,pl,mid,l,r);
	else {
		node a = cn(ls,pl,mid,l,r),b = cn(rs,mid+1,pr,l,r),c;
		c.lmax = a.lmax,c.rmax = b.rmax;
		c.sum = a.sum + b.sum;
		c.mx = max(a.mx,b.mx);
		c.l = a.l,c.r = b.r;
		if(b.l == a.r && a.r == 1) {
			if(a.sum == mid - pl + 1) c.lmax += b.lmax;
			if(b.sum == pr - mid) c.rmax += a.rmax;
			c.mx = max(c.mx,a.rmax + b.lmax);
		}
		return c;
	}
}
}


signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);

	int opt,l,r;
	cin>>n>>m;
	for(int i = 1;i<=n;i++) cin>>a[i];
	sgm::build(1,1,n);
	while(m--) {
		cin>>opt>>l>>r;
		l++,r++;
		switch(opt) {
			case 0:
				sgm::Cov(1,1,n,l,r,0);
				break;
			case 1:
				sgm::Cov(1,1,n,l,r,1);
				break;
			case 2:
				sgm::Rev(1,1,n,l,r);
				break;
			case 3:
				cout<<sgm::askSum(1,1,n,l,r)<<'\n';
				break;
			case 4:
				cout<<sgm::cn(1,1,n,l,r).mx<<'\n';
				break;
		}
	}
	return 0;
}

2024/07/11#

校内模拟赛 #2#

A.弹钢琴#

上一次写 \(dp\) 还是在上一次

最近几天,一直在写数据结构

\(dp\) 我是一点没练,成功在赛时精神不振,萎靡不振,做到了签到题耗时 \(3h\)好成绩

这里附上我当天的运势
img

题面:

题目背景

PianoEater喜欢听钢琴曲,并且一直梦想着给他的GF Little Pink弹奏一曲。于是PianoEater去钢琴王国大学(Piano Kingdom University,简称PKU)找钢琴十级的rainbow学习弹琴。
PianoEater弹琴时,他的一只手上的5根手指不能交叉,并且两根手指不能放在同一个琴键上。同时,一只手的大拇指和小指之间最多间隔7个琴键。弹琴时,左右臂可以交叉,但是用(其中一只手的手指)去按(处于另一只手的两个手指之间)的按键是不允许的。

题目描述

现在PianoEater有一架有52个白键和36个黑键的钢琴,并且他要弹奏的曲子只需要按白键。在同一时刻,他只用弹奏一个音符。如果这个音符不移动大拇指就可以按到,那么他不需要耗费体力;否则他需要花费sqrt(x)(下取整)的体力来移动手的位置(也就是移动大拇指的位置)。其中x代表移动前后大拇指的位置之差的绝对值。

现在有一首由N个音符组成的乐曲,每个音符用0~51之间的一个整数表示,分别对应了52个白键。0是最左边的键,51是最右边的键。PianoEater想知道他弹完这首曲子最少需要耗费多少体力。

输入格式

输入的第一行是三个整数,L,R,N,分别表示初始时刻左手大拇指的位置、右手大拇指的位置和乐曲的音符数。

接下来N行每行一个在0~51之间的整数,代表需要弹奏的音符。

输出格式

输出一个整数,表示最少需要耗费的体力。

样例 #1
样例输入 #1
10 20 10
0
1
2
3
4
5
6
7
8
9
样例输出 #1
2
提示

【时间限制】

1s

【题目提示】

对于30%的数据,1<=N<=100

对于50%的数据,1<=N<=500

对于100%的数据,1<=N<=1000,4<=L<=51,0<=R<=47

作为一个 \(OIer\),我是没想到还要看题面背景,导致我直接错过了重要信息:

PianoEater弹琴时,他的一只手上的5根手指不能交叉,并且两根手指不能放在同一个琴键上。同时,一只手的大拇指和小指之间最多间隔7个琴键。弹琴时,左右臂可以交叉,但是用(其中一只手的手指)去按(处于另一只手的两个手指之间)的按键是不允许的。

警钟长鸣!

直接 \(dp\) 下一个音符,左右手就好了 dp[i][L][R]

因为用什么手指按目标琴键很关键,所以要枚举

\[右手大拇指位置 \rightarrow [x - 8 \sim x] \\ 左手大拇指位置 \rightarrow [x \sim x + 8] \]

然后转移式

\[ left-hand:\ f_{i,x + l,k} \leftarrow f_{i-1,j,k} + \sqrt{\lvert j - (x + l) \rvert} \]

\[ right-hand:\ f_{i,j,x - l} \leftarrow f_{i-1,j,k} + \sqrt{\lvert (x - l) - k \rvert} \]

注意题面背景,加上

左手条件:if(x + l < k || x + l - 5 > k + 5)
右手条件:if(j < x - l || x - l + 5 < j - 5)

就大功告成了

AC-code:

#include<bits/stdc++.h>
using namespace std;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e3 + 5,inf = 0x3f3f3f3f;

int dp[N][60][60];

signed main() {
	memset(dp,0x3f,sizeof(dp));
	int L = rd(),R = rd(),n = rd();
	dp[0][L][R] = 0;
	for(int i = 1;i<=n;i++) {
		int x = rd();
		for(int j = 0;j<=51;j++) 
			for(int k = 0;k <= 51;k++) if(dp[i-1][j][k] != inf){
				for(int l = 0;l<=8;l++) if(x + l < k || x + l - 5 > k + 5) dp[i][x + l][k] = min(dp[i][x + l][k],dp[i-1][j][k] + int(sqrt(abs(j - (x + l)))));
				for(int l = 0;l<=8;l++) if(j < x - l || x - l + 5 < j - 5) dp[i][j][x - l] = min(dp[i][j][x - l],dp[i-1][j][k] + int(sqrt(abs((x - l) - k)))); 
			}
	}

	int ans = inf;
	for(int i = 0;i<=51;i++) for(int j = 0;j<=51;j++) ans = min(ans,dp[n][i][j]);
	wt(ans);


	return 0;
}

B.捉迷藏#

被骗了,这不是搜索

题面:

题目背景

vani和cl2在一片树林里捉迷藏……
这片树林里有N座房子,M条有向道路,组成了一张有向无环图。
树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。如果从房子A沿着路走下去能够到达B,那么在A和B里的人是能够相互望见的。

题目描述

现在cl2要在这N座房子里选择K座作为藏身点,同时vani也专挑cl2作为藏身点的房子进去寻找,为了避免被vani看见,cl2要求这K个藏身点的任意两个之间都没有路径相连。
为了让vani更难找到自己,cl2想知道最多能选出多少个藏身点?

输入格式

第一行两个整数N,M。

接下来M行每行两个整数x、y,表示一条从x到y的有向道路。

输出格式

一个整数K,表示最多能选取的藏身点个数。

样例 #1
样例输入 #1
4 4
1 2
3 2
3 4
4 2
样例输出 #1
2
提示

【时间限制】

1s

【题目提示】

对于20% 的数据,N≤10,M<=20。

对于60% 的数据, N≤100,M<=1000。

对于100% 的数据,N≤200,M<=30000,1<=x,y<=N。

不难想到传递闭包,判断两点之间的连通性

但是接下来就不好办了,如果暴搜,只有 \(75\) 分,让人很不愉快

然而题解是 二分图最大匹配 \(\rightarrow\) 最小点覆盖集

连通的两点 \(i,j\)\(i \rightarrow j + N\) 建边 和 \(j + N \rightarrow i\) 建边

这样就可以让每一个连通的部分只有一个被加入答案

这题对于我这个没怎么写过二分图匹配的不大友好

img

找最小点覆盖集就可以了,方法是:

\[最小点覆盖集 = 二分图点数 - 二分图最大匹配 \]

AC-code:

#include<bits/stdc++.h>
using namespace std;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 205;

bool G[N][N];

vector<int> g[N << 1];

int mch[N << 1],vis[N<<1];

bool dfs(const int u,const int tag) {
	if(vis[u] == tag) return false;
	vis[u] = tag;
	for(int v:g[u]) if((mch[v] == 0)||dfs(mch[v],tag)){
		mch[v] = u;
		return true;
	}
	return false;
}

signed main() {

	int n = rd(),m = rd();
	for(int i = 1;i<=m;i++) {
		int u = rd(),v = rd();
		G[u][v] = 1;
	}

	for(int k = 1;k<=n;k++) 
		for(int i = 1;i<=n;i++) 
			for(int j = 1;j<=n;j++)
				G[i][j] = G[i][j] | (G[i][k] & G[k][j]);

	for(int i = 1;i<=n;i++) 
		for(int j = 1;j<=n;j++) 
			if(G[i][j]) {
				g[i].push_back(j + N);
				g[j + N].push_back(i);
			}
	
	int ans = 0;

	for(int i = 1;i<=n;i++) ans += dfs(i,i);

	wt(n - ans);

	return 0;
}

C.水叮当的舞步#

或者是打开这个 UVA1505 Flood-it!

神仙 \(IDA*\)

估价函数为在场的剩余颜色种类

int F() {
	int t = 0;
	memset(f,0,sizeof(f));
	for(int i = 1;i<=n;i++) {
		for(int j = 1;j<=n;j++) {
			if(!f[m[i][j]] && v[i][j] ^ 1){
				f[m[i][j]] = 1;
				t++;
			}
		}
	}
	return t;
}

确定周围位置

void paint(int x,int y,int c){
	v[x][y] = 1;
	for(int i = 1;i<=4;i++) {
		int tx = x + d[i][0],ty = y + d[i][1];
		if((!check(tx,ty)) || v[tx][ty] == 1) continue;
		v[tx][ty] = 2;
		if(m[tx][ty] == c) paint(tx,ty,c);
	}
}

枚举每种颜色,以左上角为起点向周围染色,

int fill(int c) {
	int t = 0;
	for(int i = 1;i<=n;i++) {
		for(int j = 1;j<=n;j++) {
			if(m[i][j] == c && v[i][j] == 2) {
				t++;
				paint(i,j,c);
			}
		}
	}
	return t;
}

迭代加深搜索 AC-code:

#include<bits/stdc++.h>
using namespace std;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

#define check(x,y) ((x) >= 1 && (x) <= n && (y) >= 1 && (y) <= n)

const int N = 10;
int n;
int d[5][2] = {{0,0},{0,1},{1,0},{-1,0},{0,-1}};
int m[N][N],v[N][N],f[N];

int F() {
	int t = 0;
	memset(f,0,sizeof(f));
	for(int i = 1;i<=n;i++) {
		for(int j = 1;j<=n;j++) {
			if(!f[m[i][j]] && v[i][j] ^ 1){
				f[m[i][j]] = 1;
				t++;
			}
		}
	}
	return t;
}

void paint(int x,int y,int c){
	v[x][y] = 1;
	for(int i = 1;i<=4;i++) {
		int tx = x + d[i][0],ty = y + d[i][1];
		if((!check(tx,ty)) || v[tx][ty] == 1) continue;
		v[tx][ty] = 2;
		if(m[tx][ty] == c) paint(tx,ty,c);
	}
}

int fill(int c) {
	int t = 0;
	for(int i = 1;i<=n;i++) {
		for(int j = 1;j<=n;j++) {
			if(m[i][j] == c && v[i][j] == 2) {
				t++;
				paint(i,j,c);
			}
		}
	}
	return t;
}

int rule;

bool dfs(int dep) {
	int g = F();
	if(dep + g > rule) return false;
	if(!g) return true;
	int rec[10][10];
	for(int i = 0;i<=5;i++) {
		memcpy(rec,v,sizeof(v));
		if(fill(i) && dfs(dep + 1)) return true;
		memcpy(v,rec,sizeof(rec));
	}
	return false;
}

signed main() {

	while(1){
		memset(v,0,sizeof(v));
		n = rd();
		if(n == 0) break;
		for(int i = 1;i<=n;i++) for(int j = 1;j<=n;j++) m[i][j] = rd();
		paint(1,1,m[1][1]);
		for(rule = 0;;rule++) if(dfs(0)) break;
		wt(rule);
		putchar('\n');
	}

	
	return 0;
}

P5664 [CSP-S2019] Emiya 家今天的饭#

《深进》例题

题面:

题目描述

Emiya 是个擅长做菜的高中生,他共掌握 \(n\)烹饪方法,且会使用 \(m\)主要食材做菜。为了方便叙述,我们对烹饪方法从 \(1 \sim n\) 编号,对主要食材从 \(1 \sim m\) 编号。

Emiya 做的每道菜都将使用恰好一种烹饪方法与恰好一种主要食材。更具体地,Emiya 会做 \(a_{i,j}\) 道不同的使用烹饪方法 \(i\) 和主要食材 \(j\) 的菜(\(1 \leq i \leq n\)\(1 \leq j \leq m\)),这也意味着 Emiya 总共会做 \(\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{m} a_{i,j}\) 道不同的菜。

Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 \(k\) 道菜的搭配方案而言:

  • Emiya 不会让大家饿肚子,所以将做至少一道菜,即 \(k \geq 1\)
  • Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
  • Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 \(\lfloor \frac{k}{2} \rfloor\) 道菜)中被使用

这里的 \(\lfloor x \rfloor\) 为下取整函数,表示不超过 \(x\) 的最大整数。

这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。

Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 \(998,244,353\) 取模的结果。

输入格式

第 1 行两个用单个空格隔开的整数 \(n,m\)

第 2 行至第 \(n + 1\) 行,每行 \(m\) 个用单个空格隔开的整数,其中第 \(i + 1\) 行的 \(m\) 个数依次为 \(a_{i,1}, a_{i,2}, \cdots, a_{i,m}\)

输出格式

仅一行一个整数,表示所求方案数对 \(998,244,353\) 取模的结果。

样例 #1
样例输入 #1
2 3 
1 0 1
0 1 1
样例输出 #1
3
样例 #2
样例输入 #2
3 3
1 2 3
4 5 0
6 0 0
样例输出 #2
190
样例 #3
样例输入 #3
5 5
1 0 0 1 1
0 1 0 1 0
1 1 1 1 0
1 0 1 0 1
0 1 1 0 1
样例输出 #3
742
提示

【样例 1 解释】

由于在这个样例中,对于每组 \(i, j\),Emiya 都最多只会做一道菜,因此我们直接通过给出烹饪方法、主要食材的编号来描述一道菜。

符合要求的方案包括:

  • 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 2 的菜
  • 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 3 的菜
  • 做一道用烹饪方法 1、主要食材 3 的菜和一道用烹饪方法 2、主要食材 2 的菜

因此输出结果为 \(3 \bmod 998,244,353 = 3\)。 需要注意的是,所有只包含一道菜的方案都是不符合要求的,因为唯一的主要食材在超过一半的菜中出现,这不满足 Yazid 的要求。

【样例 2 解释】

Emiya 必须至少做 2 道菜。

做 2 道菜的符合要求的方案数为 100。

做 3 道菜的符合要求的方案数为 90。

因此符合要求的方案数为 100 + 90 = 190。

【数据范围】

测试点编号 \(n=\) \(m=\) \(a_{i,j}<\) 测试点编号 \(n=\) \(m=\) \(a_{i,j}<\)
\(1\) \(2\) \(2\) \(2\) \(7\) \(10\) \(2\) \(10^3\)
\(2\) \(2\) \(3\) \(2\) \(8\) \(10\) \(3\) \(10^3\)
\(3\) \(5\) \(2\) \(2\) \(9\sim 12\) \(40\) \(2\) \(10^3\)
\(4\) \(5\) \(3\) \(2\) \(13\sim 16\) \(40\) \(3\) \(10^3\)
\(5\) \(10\) \(2\) \(2\) \(17\sim 21\) \(40\) \(500\) \(10^3\)
\(6\) \(10\) \(3\) \(2\) \(22\sim 25\) \(100\) \(2\times 10^3\) \(998244353\)

对于所有测试点,保证 \(1 \leq n \leq 100\)\(1 \leq m \leq 2000\)\(0 \leq a_{i,j} \lt 998,244,353\)

首先,我们不急着考虑全部 \(3\) 个要求,先试着考虑前两个要求,

对于任意一种烹饪方法 \(i\),选择的菜数取值在 \([0 \sim \sum_{j = 1}^{m} a_{i,j}]\) 之中,共 \(1 + \sum_{j = 1}^{m} a_{i,j}\) 种选择。

对于整体,则有 \(\prod_{i = 1}^n (1 + \sum_{j = 1}^m a_{i,j})\) 中选择,但是我们不能让所有人饿肚子,故方案总数为

\[[\prod_{i = 1}^n (1 + \sum_{j = 1}^m a_{i,j})] - 1 \]

然后,割去不满足第 \(3\) 种情况的方案

我们设 \(f_{i,k,c}\) 为 前 \(i\) 种烹饪方式,选择了 \(k\) 种菜品,其中第 \(x\) 种菜品拿了 \(c\) 个的方案数

不难有转移:

\[\large f_{i,k,c} \leftarrow^{[j = x]} f_{i-1,j-1,c-1} \cdot a_{i,j} \\ \large f_{i,k,c} \leftarrow^{[j \not = x]} f_{i-1,j-1,c} \cdot a_{i,j}\\ \large f_{i,k,c} \leftarrow^{nothing\ happened} f_{i-1,j,c} \]

很遗憾,我们要枚举 \(i,k,c,x\),完美的来到了 \(O(mn^3)\),不能接受

我们要开启人类智慧,放缩优化状态

我们有 \(c > \lfloor \frac{k}{2} \rfloor,c\) 是一个整数

那么,

\[c > \lfloor \frac{k}{2} \rfloor \Rightarrow^{k \in odd} c > \frac{k}{2} - \frac{1}{2} \Rightarrow 2c + 1 > k \]

\[c > \lfloor \frac{k}{2} \rfloor \Rightarrow^{k \in even} c > \frac{k}{2} \Rightarrow 2c > k \]

\(k \in odd\) 时,由于 \(2c + 1 \in odd\),那么 \(2c \geq k + 1\),所以 \(c > \frac{k}{2} \Rightarrow 2c > k\)

所以,\(2c > k \rightarrow 2c - k > 0\)

然后让我们变换一下,得到 \(c - (k - c) > 0\)

这个式子的含义是不符合条件的方案必须满足 \(超过一半总数的菜品 - 剩下的菜品 > 0\)(虽然很显然,但是还是用理论推导了一遍),

那么我们最后只要关注这个 \(\Delta = 超过一半总数的菜品 - 剩下的菜品 > 0\) 的方案数,累加即可得到所有不符合条件的方案数

那么,我们重新设 \(f_{i,\Delta}\)

有转移:

\[f_{i,\Delta + 1} \leftarrow^{j = x} a_{i,j} \cdot f_{i-1,\Delta} \]

\[f_{i,\Delta - 1} \leftarrow^{j \not= x} a_{i,j}\cdot f_{i-1,\Delta} \]

最后注意 \(\Delta\) 会是负数,扩大一倍去维护就好了

结束!

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 105,M = 2e3+5;

const int mod = 998244353;

int S[N],d[N][M],dp[N][N<<1];

signed main() {

	int n = rd(),m = rd();

	int prod = 1;
	for(int i = 1;i<=n;i++) {
		for(int j = 0;j<m;j++) {
			d[i][j] = rd();
			S[i] = (S[i] + d[i][j]) % mod;
		}
		prod = 1ll * prod * (S[i] + 1) % mod;
	}

	int ans = (prod + mod - 1) % mod;

	for(int x = 0;x < m;x++) {
		memset(dp,0,sizeof(dp));
		dp[0][0 + 101] = 1;
		int no = 0;
		for(int i = 1;i<=n;i++) {
			int re = (S[i] + mod - d[i][x]) % mod;
			for(int dt = 1;dt <= 201;dt++) {
				dp[i][dt + 1] = (dp[i][dt + 1] + 1ll * dp[i-1][dt] * d[i][x] % mod) % mod;
				dp[i][dt - 1] = (dp[i][dt - 1] + 1ll * dp[i-1][dt] * re % mod) % mod;
				dp[i][dt] = (dp[i][dt] + dp[i-1][dt]) % mod;
			}
		}
		for(int dt = 1;dt<=n;dt++) no = (no + dp[n][dt + 101]) % mod;
		ans = (ans + mod - no) % mod;
	}

	wt(ans);

	return 0;
}

2024/07/15#

进入培训阶段

先写个 \(AC\) 自动机(简单版)

P3808 AC 自动机(简单版)#

题面:

AC 自动机(简单版)
题目描述

给定 \(n\) 个模式串 \(s_i\) 和一个文本串 \(t\),求有多少个不同的模式串在文本串里出现过。
两个模式串不同当且仅当他们编号不同。

输入格式

第一行是一个整数,表示模式串的个数 \(n\)
\(2\) 到第 \((n + 1)\) 行,每行一个字符串,第 \((i + 1)\) 行的字符串表示编号为 \(i\) 的模式串 \(s_i\)
最后一行是一个字符串,表示文本串 \(t\)

输出格式

输出一行一个整数表示答案。

样例 #1
样例输入 #1
3
a
aa
aa
aaa
样例输出 #1
3
样例 #2
样例输入 #2
4
a
ab
ac
abc
abcd
样例输出 #2
3
样例 #3
样例输入 #3
2
a
aa
aa
样例输出 #3
2
提示
样例 1 解释

\(s_2\)\(s_3\) 编号(下标)不同,因此各自对答案产生了一次贡献。

样例 2 解释

\(s_1\)\(s_2\)\(s_4\) 都在串 abcd 里出现过。

数据规模与约定
  • 对于 \(50\%\) 的数据,保证 \(n = 1\)
  • 对于 \(100\%\) 的数据,保证 \(1 \leq n \leq 10^6\)\(1 \leq |t| \leq 10^6\)\(1 \leq \sum\limits_{i = 1}^n |s_i| \leq 10^6\)\(s_i, t\) 中仅包含小写字母。

KMP对于多个模式串无能为力,那么AC自动机就出现了

首先是字典树插入

void insert(char *s) {
	int p = 0;
	for(int i = 0;s[i];i++) {
		int j = s[i] - 'a';
		if(!ch[p][j]) ch[p][j] = ++idx;
		p = ch[p][j];
	}
	cnt[p]++;
}

然后是建立AC自动机

void build() {
	queue<int> q;
	for(int i = 0;i<26;i++) 
		if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty()) {
		int u = q.front();
		q.pop();
		for(int i = 0;i<26;i++) {
			int v = ch[u][i];
			if(v) nxt[v] = ch[nxt[u]][i],q.push(v); // 这里是建回跳链,主要是看后缀
			else ch[u][i] = ch[nxt[u]][i];// 这里是转移链,匹配其他串
		}
	}
}

最后查询:

int query(char *s) {
	int ans = 0;
	for(int k = 0,i = 0;s[k];k++) {
		i = ch[i][s[k] - 'a'];//走串
		for(int j = i;j && ~cnt[j];j = nxt[j])//跳链,判断是否回到 0 节点/ 串是否已经被访问
			ans += cnt[j],cnt[j] = -1;//累加结果,然后打上标记,表示此串已经被访问
	}
	return ans;
}

AC-code:

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6 + 5;

int ch[N][26],nxt[N],cnt[N],idx;

void insert(char *s) {
	int p = 0;
	for(int i = 0;s[i];i++) {
		int j = s[i] - 'a';
		if(!ch[p][j]) ch[p][j] = ++idx;
		p = ch[p][j];
	}
	cnt[p]++;
}

void build() {
	queue<int> q;
	for(int i = 0;i<26;i++) 
		if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty()) {
		int u = q.front();
		q.pop();
		for(int i = 0;i<26;i++) {
			int v = ch[u][i];
			if(v) nxt[v] = ch[nxt[u]][i],q.push(v);
			else ch[u][i] = ch[nxt[u]][i];
		}
	}
}

int query(char *s) {
	int ans = 0;
	for(int k = 0,i = 0;s[k];k++) {
		i = ch[i][s[k] - 'a'];
		for(int j = i;j && ~cnt[j];j = nxt[j])
			ans += cnt[j],cnt[j] = -1;
	}
	return ans;
}

int n;
char s[N],t[N];

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	
	cin>>n;
	for(int i = 1;i<=n;i++) {
		cin>>s;
		insert(s);
	}
	cin>>t;
	build();
	cout<<query(t);

	return 0;
}

2024/07/17#

LEGOndary Grandmaster#

题面:

题面翻译

对于两个长度为 \(n\)\(01\)\(s,t\) ,你可以对 \(s\) 进行两种操作:把相邻两个 \(0\) 变成 \(1\) 或把相邻两个 \(1\) 变成 \(0\) ,定义 \(s\)\(t\) 的距离为最少操作次数使得 \(s\) 变成 \(t\) ,如过没法变则距离为 \(0\)

现在你有两个不完整的字符串,可以把其中的 \(?\) 变成 \(0\)\(1\) ,求所有情况所得到的两个 \(01\) 串的距离之和。

有趣的结论:

\[00 \rightarrow 11 , 11 \rightarrow 00 \Rightarrow^{在偶数位取反} 10 \rightarrow 01,01 \rightarrow 10,即 ‘交换’ \]

最小步数:\(\sum_i \lvert a_i - b_i \rvert\)

待补!


最近唐完了,发生了各种各样的意外。

因为在集训,没来的及在 NOI之前给 ckain 送祝福

所以在这里,我祝 ckain 学长能在 NOI 赛场上叱诧风云,做一个风云人物 !

愿上天保佑你


P2668 [NOIP2015 提高组] 斗地主#

大模拟,唯一关键的点在于:只剩下 炸弹,对子,单张,只需要一次就可以打完,所以最后判断该数码是否还有牌,有牌就加1,否则跳过

其他都是细节

AC-code:

#include<bits/stdc++.h>
using namespace std;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

int n,t[20],ans = 0x3f3f3f3f;

void dfs(int dep){
	if(dep >= ans) return;
	int k = 0;
	for(int i = 3;i<=14;i++) {
		if(t[i] == 0) k = 0;
		else {
			k++;
			if(k >= 5) {
				int l = i - k + 1;
				for(int j = l;j<=i;j++) t[j]--;
				dfs(dep + 1);
				for(int j = l;j<=i;j++) t[j]++;
			}
		}
	}
	k = 0;
	for(int i = 3;i<=14;i++){
		if(t[i] <= 1) k = 0;
		else {
			k++;
			if(k >= 3) {
				int l = i - k + 1;
				for(int j = l;j<=i;j++) t[j] -= 2;
				dfs(dep + 1);
				for(int j = l;j<=i;j++) t[j] += 2;
			}
		}
	}
	k = 0;
	for(int i = 3;i<=14;i++) {
		if(t[i] <= 2) k = 0;
		else {
			k++;
			if(k >= 2) {
				int l = i - k + 1;
				for(int j = l;j<=i;j++) t[j] -= 3;
				dfs(dep + 1);
				for(int j = l;j<=i;j++) t[j] += 3;
			}
		}
	}

	for(int i = 3;i<=15;i++) {
		if(t[i] <= 3) {
			if(t[i] < 3) continue;
			t[i] -= 3;
			for(int j = 0;j<=15;j++) {
				if(i == j || j == 1 || j == 2 || t[j] == 0) continue;
				t[j]--;
				dfs(dep + 1);
				t[j]++;
			}
			for(int j = 3;j<=15;j++) {
				if(i == j || t[j]  <= 1) continue;
				t[j] -= 2;
				dfs(dep + 1);
				t[j] += 2;
			}
			t[i] += 3;
		}else {
			t[i] -= 3;
			for(int j = 3;j<=15;j++) {
				if(i == j || t[j] == 0) continue;
				t[j]--;
				dfs(dep + 1);
				t[j]++;
			}
			for(int j = 3;j<=15;j++) {
				if(i == j || t[j]  <= 1) continue;
				t[j] -= 2;
				dfs(dep + 1);
				t[j] += 2;
			}
			t[i] += 3;

			t[i] -= 4;
			for(int j = 0;j<=15;j++) {
				if(t[j] == 0 || j == 1 || j == 2 || j == i) continue;
				t[j]--;
				for(int k = 0;k<=15;k++) {
					if(t[k] == 0 || k == 1 || k == 2 || k == j) continue;
					t[k]--;
					dfs(dep + 1);
					t[k]++;
				}
				t[j]++;
			}
			for(int j = 3;j<=15;j++) {
				if(t[j] <= 1) continue;
				t[j] -= 2;
				for(int k = 3;k<=15;k++){
					if(t[k] <= 1) continue;
					t[k] -= 2;
					dfs(dep + 1);
					t[k] += 2;
				}
				t[j] += 2;
			}
			t[i] += 4;
		}
	}
	int res = dep;
	for(int i = 0;i<=15;i++) {
		if(i == 1 || i == 2) continue;
		res += (t[i]) ? 1:0;
	}
	ans = min(res,ans);
}

void solve() {
	memset(t,0,sizeof(t));
	ans = 0x3f3f3f3f;
    for(int i = 1;i<=n;i++) t[rd()]++,rd();
	t[1 + 13] = t[1];
	t[2 + 13] = t[2];
	t[1] = t[2] = 0;
	dfs(0);
	wt(ans);putchar('\n');
}

signed main() {
    int T;
	T = rd(),n = rd();
    while(T--) solve(); 

	return 0;
}

P2668 [NOIP2015 提高组] 斗地主 加强版#

同上,但是上面的代码有瑕疵

  1. 四带二,没说就是同类也可以带,如:4444 带 22 和 22
  2. 超时了!怎么办 -> 记忆化,直接用 unordered_map hash 局面,我们发现花色啥用没有,我们只关心每种牌的数量,所以我们可以压到一个字符串里,记录这个串到清牌需要几步,(初始化为 inf,且在使用时判断是否 < inf)

做好这两步,本题就解决了
AC-code:

#include<bits/stdc++.h>
using namespace std;

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

int n,t[20],ans = 0x3f3f3f3f,inf = 0x3f3f3f3f;

unordered_map<string,int> f;

int dfs(int dep){
	if(dep >= ans) return inf;

	string s;
	for(int i = 0;i<=15;i++) s.push_back(t[i] + '0');
	if(f.count(s) && f[s] != inf) {
		ans = min(ans,f[s] + dep);
		return f[s] + dep;
	}else f[s] = inf;
	int k = 0;
	for(int i = 3;i<=14;i++) {
		if(t[i] == 0) k = 0;
		else {
			k++;
			if(k >= 5) {
				int l = i - k + 1;
				for(int j = l;j<=i;j++) t[j]--;
				f[s] = min(f[s],dfs(dep + 1) - dep);
				for(int j = l;j<=i;j++) t[j]++;
			}
		}
	}
	k = 0;
	for(int i = 3;i<=14;i++){
		if(t[i] <= 1) k = 0;
		else {
			k++;
			if(k >= 3) {
				int l = i - k + 1;
				for(int j = l;j<=i;j++) t[j] -= 2;
				f[s] = min(f[s],dfs(dep + 1) - dep);
				for(int j = l;j<=i;j++) t[j] += 2;
			}
		}
	}
	k = 0;
	for(int i = 3;i<=14;i++) {
		if(t[i] <= 2) k = 0;
		else {
			k++;
			if(k >= 2) {
				int l = i - k + 1;
				for(int j = l;j<=i;j++) t[j] -= 3;
				f[s] = min(f[s],dfs(dep + 1) - dep);
				for(int j = l;j<=i;j++) t[j] += 3;
			}
		}
	}

	for(int i = 3;i<=15;i++) {
		if(t[i] <= 3) {
			if(t[i] < 3) continue;
			t[i] -= 3;
			for(int j = 0;j<=15;j++) {
				if(i == j || j == 1 || j == 2 || t[j] == 0) continue;
				t[j]--;
				f[s] = min(f[s],dfs(dep + 1) - dep);
				t[j]++;
			}
			for(int j = 3;j<=15;j++) {
				if(i == j || t[j]  <= 1) continue;
				t[j] -= 2;
				f[s] = min(f[s],dfs(dep + 1) - dep);
				t[j] += 2;
			}
			t[i] += 3;
		}else {
			t[i] -= 3;
			for(int j = 3;j<=15;j++) {
				if(i == j || t[j] == 0) continue;
				t[j]--;
				f[s] = min(f[s],dfs(dep + 1) - dep);
				t[j]++;
			}
			for(int j = 3;j<=15;j++) {
				if(i == j || t[j]  <= 1) continue;
				t[j] -= 2;
				f[s] = min(f[s],dfs(dep + 1) - dep);
				t[j] += 2;
			}
			t[i] += 3;

			t[i] -= 4;
			for(int j = 0;j<=15;j++) {
				if(t[j] == 0 || j == 1 || j == 2 || j == i) continue;
				t[j]--;
				for(int k = 0;k<=15;k++) {
					if(t[k] == 0 || k == 1 || k == 2) continue;
					t[k]--;
					f[s] = min(f[s],dfs(dep + 1) - dep);
					t[k]++;
				}
				t[j]++;
			}
			for(int j = 3;j<=15;j++) {
				if(t[j] <= 1) continue;
				t[j] -= 2;
				for(int k = 3;k<=15;k++){
					if(t[k] <= 1) continue;
					t[k] -= 2;
					f[s] = min(f[s],dfs(dep + 1) - dep);
					t[k] += 2;
				}
				t[j] += 2;
			}
			t[i] += 4;
		}
	}
	int res = dep;
	for(int i = 0;i<=15;i++) {
		if(i == 1 || i == 2) continue;
		res += (t[i]) ? 1:0;
	}
	ans = min(res,ans);
	return res;
}

void solve() {
	memset(t,0,sizeof(t));
	f.clear();
	ans = 0x3f3f3f3f;
    for(int i = 1;i<=n;i++) t[rd()]++,rd();
	t[1 + 13] = t[1];
	t[2 + 13] = t[2];
	t[1] = t[2] = 0;
	dfs(0);
	wt(ans);putchar('\n');
}

signed main() {
    int T;
	T = rd(),n = rd();
    while(T--) solve(); 

	return 0;
}

2024/07/19#

Polo the Penguin and Trees#

题面:

题面翻译

题意:给定 \(n\) 个节点的树,求满足条件的四元组 \((a,b,c,d)\) 的数量:

  • \(1\leq a< b\leq n\quad 1\leq c< d\leq n\)

  • \(a\)\(b\)\(c\)\(d\) 的路径没有交点

\(n\leq 8\times 10^4\)

样例 #1
样例输入 #1
4
1 2
2 3
3 4
样例输出 #1
2

还是 正难则反 的思想

所有的四元对有 \(\dbinom{n}{2}^2\)

那么我们考虑相交的方案

首先,我们考虑两条路径不在一颗子树的方案

那么这两条路径在哪交呢?

我们钦定 \(p\) 节点为两条路径的交

那么在 \(p\) 子树内经过 \(p\) 的线段有 \(\dbinom{siz_p}{2} - \sum_{q \in son_p}\dbinom{siz_q}{2}\)

不在 \(p\) 子树的结点有 \(n - siz_p\)

那么经过 \(p\) 节点但不在 \(p\) 子树内的线段有 \((n - siz_p)siz_p\)

那么两条路径都在 \(p\) 的四元对有 \(a = (\dbinom{siz_p}{2} - \sum_{q \in son_p}\dbinom{siz_q}{2}) ^ 2\)

两条路径不在同一子树上的四元对有 \(b = 2(siz_p(n - siz_p)(\dbinom{siz_p}{2} - \sum_{q \in son_p}\dbinom{siz_q}{2}))\)

最后的答案为 $ \dbinom{n}{2}^2 - a - b$

AC-code:


#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 80005;

int n,head[N],nxt[N<<1],to[N<<1],cnt;

void init() {memset(head,-1,sizeof(head));}

void add(int u,int v) {
    nxt[cnt] = head[u];
    to[cnt] = v;
    head[u] = cnt++;
}

int siz[N],fa[N],del;

void dfs1(int x,int f) {
    siz[x] = 1;
    fa[x] = f;
    for(int i = head[x];~i;i = nxt[i]) {
        int y = to[i];
        if(y ^ f) {
            dfs1(y,x);
            siz[x] += siz[y];
        }
    }
}

int C2(int x) {
	if(x < 0) return 0;
	return x * (x - 1) / 2;
}

void dfs2(int x) {
    int ans1 = C2(siz[x]),ans2 = (n - siz[x]) * siz[x];
    for(int i = head[x];~i;i = nxt[i]) {
        int y = to[i];
        if(y ^ fa[x]) {
            dfs2(y);
            ans1 -= C2(siz[y]); 
        }
    }
    del += ans1 * ans2 * 2 + ans1 * ans1;
}

// 正难则反

signed main() {
    init();
	n = rd();
    for(int i = 1,u,v;i<n;i++) {
        u = rd(),v = rd();
        add(u,v);add(v,u);
    }
    int k = 0;
    dfs1(1,0);
    dfs2(1);
    wt(C2(n) * C2(n) - del);

	return 0;
}

The Bakery#

题面:

题面翻译

将一个长度为 \(n\) 的序列分为 \(k\) 段,使得总价值最大。

一段区间的价值表示为区间内不同数字的个数。

\(n\leq 35000,k\leq 50\)

Translated by @ysner @yybyyb

样例 #1
样例输入 #1
4 1
1 2 2 1
样例输出 #1
2
样例 #2
样例输入 #2
7 2
1 3 3 1 4 4 4
样例输出 #2
5
样例 #3
样例输入 #3
8 3
7 7 8 7 7 8 1 7
样例输出 #3
6

线段树优化的典题,但是我们要用四边形不等式

以此为四边形不等式优化dp的模板

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 35005;

int L = 1,R,ans,cnt[N];

int n,k;
int f[N][51],a[N];

void add(int x) {
	if(!cnt[x]) ans++;
	cnt[x]++;
}
void del(int x) {
	cnt[x]--;
	if(!cnt[x]) ans--;
}

int val(int l,int r) {
    while(L > l) add(a[--L]);
    while(R < r) add(a[++R]);
    while(R > r) del(a[R--]);
    while(L < l) del(a[L++]);
    return ans;
}

void solve(int pl,int pr,int l,int r,int tot) {
    if(pl > pr) return;
    int mid = (pl + pr) >> 1,qmid = l;
    for(int i = l;i <= min(r,mid);i++) {
        int las = f[i - 1][tot - 1] + val(i,mid);
        bool update = las > f[mid][tot];
        if(update) f[mid][tot] = las,qmid = i;//找最优分割点
    }
    solve(pl,mid - 1,l,qmid,tot);
    solve(mid + 1,pr,qmid,r,tot);
}

signed main() {
	n = rd(),k = rd();
    for(int i = 1;i<=n;i++) a[i] = rd();
    memset(f,0xcf,sizeof(f));
    f[0][0] = 0;
    for(int i = 1;i<=k;i++) solve(1,n,1,n,i);
    wt(f[n][k]);
	return 0;
}

什么情况满足四边形不等式呢?

来自梦熊联盟课件

考虑如下问题:\(f(i)=\min_{1\leq j\leq i}w(j,i)\)

如果对任意 \(a,b,c,d(a\leq b\leq c\leq d)\) 满足 \(w(a,c)+w(b,d)\leq w(a,d)+w(b,c)\),则称 \(w\) 满足四边形不等式。

哪些满足四边形不等式?

  1. 如果 \(w(j,i)\) 满足四边形不等式,那么 \(w'(j,i)=f(j-1)+w(j,i)\) 也满足四边形不等式。

    \[\begin{aligned} w(a,c)+w(b,d)&\leq w(a,d)+w(b,c)\\ f(a-1)+f(b-1)+w(a,c)+w(b,d)&\leq f(a-1)+f(b-1)+w(a,d)+w(b,c)\\ (f(a-1)+w(a,c))+(f(b-1)+w(b,d))&\leq (f(a-1)+w(a,d))+(f(b-1)+w(b,c))\\ \end{aligned} \]

    \(f(i)=\min_{1\leq j\leq i}f(j-1)+w(j,i)\)

  2. \(w(j,i)=v_j\)

    \[w(a,c)+w(b,d)\leq w(a,d)+w(b,c)\\ v_a+v_b\leq v_a+v_b \]

  3. \(w(j,i)=v_i\)

  4. \(w(j,i)=ji\)

    \[w(a,c)+w(b,d)\geq w(a,d)+w(b,c)\\ ac+bd\geq ad+bc\\ b(d-c)\geq a(d-c)\\ b\geq a \]

  5. \(w(j,i)=(i-j)^p\)\(p\geq 1\)

    证明要用到微积分知识,略去。

  6. 有另一个非负数组 \(v\),且 \(w(j,i)=\sum_{j\leq l\leq r\leq i}v(l,r)\)

  7. \(w(j,i)=h(i-j)\)\(h\) 是下凸函数。

2024/07/20#

mx_模拟赛#

唐完了

D.几何题(triangle)#

题目描述

在小学的课堂上,你学会了如何判断三条边是否能组成一个三角形:若三条边长为 x,y,z,如果 x+y>z,x+z>y,y+z>x 均成立,它们就能组成一个周长为 x+y+z 的三角形。

现在有 n 根木棍,第 i 根的长度是 a_i,你需要支持 q 次操作:

  • 0 p v,令 a_p=v
  • 1 l r,询问如果仅使用第 l,l+1,\dots,r 根木棍中的三根来组成三角形,且每条边恰由一根木棍组成,你能得到的三角形周长最大是多少。如果不能构成三角形,则回答 0

少些一个 ‘=’ ,少了 \(95\) 分,警钟长鸣

首先,我们来看一看组成三角形的条件:

\[a + b \leq c \]

那么,如何在众多数中找出周长最大的合法三角形呢?

我们考虑贪心,对数进行从大到小排序:

不难看出:

\[对于连续的 3 个数 —— i,j,k (i + 2 = j + 1 = k) \\ 如果 a_i + a_j \leq a_k \\ 那么对于 p,q \leq j, a_p + a_q \leq a_i + a_j \leq a_k,即 a_p + a_q 绝对不优 \]

那么,我们只需要用一个 连续三元组[i,j,k]滑动窗口计算就可以了,时间复杂度 \(O(n)\)

\[对于连续四元组 [a,b,c],假如 \\ a + b \leq c \bigwedge a \leq b \\ 那么 a \leq \frac{c}{2} \]

简单来说,对于一列数,如果这一列数 \((len \geq 2)\) 不能构成任何三角形,不连续序列数的数量为 \(O(log_2n)\)

接下来是套路,我们要存多少数呢? \(log_2 (5\times 10 ^ 8) \approx 30\)

如果读者写过 P5478,这题就变成了弟中之弟

现在问题就很明晰了(我在赛场上不到 \(10\) min 就想到了)

用线段树维护区间前 \(30\) 个数,\(push\_ up\) 用归并排序上传

可惜赛时细节直接来了一次暴扣

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}
const int N = 2e5+5;
int A[N];

namespace sgt{

#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

struct node{
	int r[30],len;
	node(){memset(r,0,sizeof(r));}
}t[N<<2];

node merge(node a,node b){
	node c;
	c.len = 0;
	int i = 1,j = 1;
	while(i <= a.len && j <= b.len && c.len < 29) {
		if(a.r[i] > b.r[j]) c.r[++c.len] = a.r[i++];
		else c.r[++c.len] = b.r[j++];
	}
	while(i <= a.len && c.len < 29) c.r[++c.len] = a.r[i++];
	while(j <= b.len && c.len < 29) c.r[++c.len] = b.r[j++];
	return c;
}

void push_up(int p) {
    t[p] = merge(t[ls],t[rs]);
}

void build(int p,int pl,int pr) {
    if(pl == pr) {
    	t[p].r[1] = A[pl];
        t[p].len = 1;
        return;
    }
    build(ls,pl,mid);
    build(rs,mid+1,pr);
    push_up(p);
}

void update(int p,int pl,int pr,int k,int d) {
    if(pl == pr) {
        t[p].r[1] = d;
        t[p].len = 1;
        return;
    }
    if(k <= mid) update(ls,pl,mid,k,d);
    else update(rs,mid+1,pr,k,d);
    push_up(p); 
}

node query(int p,int pl,int pr,int l,int r) {
    if(l <= pl && pr <= r) return t[p];
    if(r <= mid) return query(ls,pl,mid,l,r);
    else if(l > mid) return query(rs,mid+1,pr,l,r);
    else {
        node T = query(ls,pl,mid,l,r),Q = query(rs,mid + 1,pr,l,r);
        node c = merge(T,Q);
        return c;
    } 
}
#undef ls
#undef rs
#undef mid
}

int n,q;

void getans(sgt::node P) {
    int i = 1,j = 2,k = 3;
    if(P.len < 3) goto End;
    for(;k<=P.len;i++,j++,k++) {
        if(P.r[i] < P.r[j] + P.r[k]) {
            wt(P.r[i] + P.r[j] + P.r[k]);
            putchar('\n');
            return;
        }
    }
    End:
    putchar('0'),putchar('\n');
}

signed main() {
	freopen("triangle.in","r",stdin);
	freopen("triangle.out","w",stdout);
	n = rd(),q = rd();
    for(int i = 1;i<=n;i++) A[i] = rd();
    sgt::build(1,1,n);
    while(q--) {
        int opt = rd();
        if(opt) {
            int l = rd(),r = rd();
            getans(sgt::query(1,1,n,l,r));
        }else {
            int p = rd(),v = rd();
            sgt::update(1,1,n,p,v);
        }
    }
	return 0;
}

A. 随机(random)#

题面:

题目描述

给一个随机的1-n的排列,求这个排列所有子区间,一共有\frac{n\times (n+1)}{2}个,他们每一个的中位数之和。

中位数的定义是所以数从小到大排序之后的第floor(\frac{(n+1)}{2})

这题只要出 \(90\)\((n \leq 2500)\) 就可以了

那么,我的思路是(离散化后线段树上询问前 \(k\) 大值 + 双指针区间询问)

不多说,上 \(AC\) 代码

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 3e4+5;

int n,a[N],b[N],c[N];

namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

int t[N<<2];

void push_up(int p) {
    t[p] = t[ls] + t[rs];
}

void update(int p,int pl,int pr,int k,int d) {
    if(pl == pr) {
        t[p] += d;
        return;
    }
    if(k <= mid) update(ls,pl,mid,k,d);
    else update(rs,mid+1,pr,k,d);
    push_up(p);
} 

int kth(int p,int pl,int pr,int k) {
    if(pl == pr) return pl;
    if(t[ls] >= k) return kth(ls,pl,mid,k);
    return kth(rs,mid+1,pr,k - t[ls]);
}

#undef ls
#undef rs
#undef mid
}



signed main() {
    freopen("random.in","r",stdin);
    freopen("random.out","w",stdout);

    n = rd();
    for(int i = 1;i<=n;i++) a[i] = rd();
    for(int i = 1;i<=n;i++) c[i] = b[i] = a[i];
    sort(b + 1,b + n + 1);
    int top = unique(b + 1,b + n + 1) - b - 1;
    for(int i = 1;i<=n;i++) c[i] = lower_bound(b + 1,b + top + 1,c[i]) - b;
    int ans = 0;
    for(int i = 1;i<=n;i++) ans += a[i];
    int l = 1,r = 0;
    for(int i = 1;i<n;i += 2) {
        for(int j = i + 1;j<=n;j++) {
            while(l > i) sgt::update(1,1,n,c[--l],1);
            while(r < j) sgt::update(1,1,n,c[++r],1);
            while(l < i) sgt::update(1,1,n,c[l++],-1);
            while(r > j) sgt::update(1,1,n,c[r--],-1);
            int len = j - i + 1;
            if(len & 1) ans += b[sgt::kth(1,1,n,(len + 1) / 2)];
			else ans += b[sgt::kth(1,1,n,len/2)];
        }
        int k = i + 1;
        if(k >= n) break;
        for(int j = n;j>k;j--) {
            while(l > k) sgt::update(1,1,n,c[--l],1);
            while(r < j) sgt::update(1,1,n,c[++r],1);
            while(l < k) sgt::update(1,1,n,c[l++],-1);
            while(r > j) sgt::update(1,1,n,c[r--],-1);
            int len = j - k + 1;
            if(len & 1) ans += b[sgt::kth(1,1,n,(len + 1) / 2)];
			else ans += b[sgt::kth(1,1,n,len/2)];
        }
    }

    wt(ans);
	

	return 0;
}

2024/07/21#

放假,好耶!

来点整数分块提提神

P2261 [CQOI2007] 余数求和#

题面:

题目描述

给出正整数 \(n\)\(k\),请计算

\[G(n, k) = \sum_{i = 1}^n k \bmod i \]

其中 \(k\bmod i\) 表示 \(k\) 除以 \(i\) 的余数。

输入格式

输入只有一行两个整数,分别表示 \(n\)\(k\)

输出格式

输出一行一个整数表示答案。

样例 #1
样例输入 #1
10 5
样例输出 #1
29
提示
样例 1 解释

\(G(10, 5)=0+1+2+1+0+5+5+5+5+5=29\)

数据规模与约定
  • 对于 \(30\%\) 的数据,保证 \(n , k \leq 10^3\)
  • 对于 \(60\%\) 的数据,保证 \(n, k \leq 10^6\)
  • 对于 \(100\%\) 的数据,保证 \(1 \leq n, k \leq 10^9\)

2024/2/13 添加一组 hack 数据

我们要求什么?拆式子!

\[G(n,k) = \sum_{i = 1}^n (k\ \%\ i) = \sum_{i = 1}^n(k - i \cdot \lfloor \frac{k}{i} \rfloor) \\ G(n,k) = nk - \sum_{i = 1}^n (i \cdot \lfloor \frac{k}{i} \rfloor) \]

\(nk\) 部分可以 \(O(1)\) 算,废话
现在我们要求什么?分解问题!

\([1 \sim n]\) 太广了,那么我们进行整数分块!
当一个块左端点为 \(l\),那么右端点就可以求出,为 \(\lfloor \frac{k}{\lfloor \frac{k}{l} \rfloor}\rfloor\)

那么对于一个左端点为 \(l\) 的块,对于答案 \(ans\)

\[ans \leftarrow (ans - (k / l) \sum_{i = l}^r i \Leftrightarrow ans - (k / l) \cdot (r - l + 1) \cdot (l + r) / 2) \]

时间复杂度 \(O(\sqrt{n})\)
此题结束!
AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

int n,k;

signed main() {
	int l,r,res = 0;
	n = rd(),k = rd();
	res += n * k;
	for(int l = 1;l<=n;l = r + 1){
		if(k / l == 0) break;
		r = min(n,k / (k / l));
		res -= (k / l) * (r - l + 1)*(r + l) / 2;
	}
	wt(res);

	return 0;
}

P4001 [ICPC-Beijing 2006] 狼抓兔子#

题面:

题目描述

现在小朋友们最喜欢的"喜羊羊与灰太狼"。话说灰太狼抓羊不到,但抓兔子还是比较在行的,而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:

左上角点为 \((1,1)\),右下角点为 \((N,M)\)(上图中 \(N=3\)\(M=4\))。有以下三种类型的道路:

  1. \((x,y)\rightleftharpoons(x+1,y)\)

  2. \((x,y)\rightleftharpoons(x,y+1)\)

  3. \((x,y)\rightleftharpoons(x+1,y+1)\)

道路上的权值表示这条路上最多能够通过的兔子数,道路是无向的。左上角和右下角为兔子的两个窝,开始时所有的兔子都聚集在左上角 \((1,1)\) 的窝里,现在它们要跑到右下角 \((N,M)\) 的窝中去,狼王开始伏击这些兔子。当然为了保险起见,如果一条道路上最多通过的兔子数为 \(K\),狼王需要安排同样数量的 \(K\) 只狼,才能完全封锁这条道路,你需要帮助狼王安排一个伏击方案,使得在将兔子一网打尽的前提下,参与的狼的数量要最小。因为狼还要去找喜羊羊麻烦。

输入格式

第一行两个整数 \(N,M\),表示网格的大小。

接下来分三部分。

第一部分共 \(N\) 行,每行 \(M-1\) 个数,表示横向道路的权值。

第二部分共 \(N-1\) 行,每行 \(M\) 个数,表示纵向道路的权值。

第三部分共 \(N-1\) 行,每行 \(M-1\) 个数,表示斜向道路的权值。

输出格式

输出一个整数,表示参与伏击的狼的最小数量。

样例 #1
样例输入 #1
3 4
5 6 4
4 3 1
7 5 3
5 6 7 8
8 7 6 5
5 5 5
6 6 6
样例输出 #1
14
提示
数据规模与约定

对于全部的测试点,保证 \(3 \leq N,M \leq 1000\),所有道路的权值均为不超过 \(10^6\) 的正整数。

关键在 网格图转对偶图

然后建图跑最短路

最恶心的是建图

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

constexpr int N = 1005,M = 6e6+5;
int n,m;

int head[2100000],nxt[M],to[M],cnt,val[M];

void init() {memset(head,-1,sizeof(head));}

void add(int u,int v,int w) {
	nxt[cnt] = head[u];
	to[cnt] = v;
	val[cnt] = w;
	head[u] = cnt++;
}

int a[N][N],b[N][N],c[N][N];
int S,E,dis[2100000];
bool vis[2100000];

struct node{
	int x,w;
	node(int x,int w) :x(x),w(w){}
	bool operator < (const node &a) const{
		return a.w < w;
	}
};

void dij(){
	memset(dis,0x3f,sizeof(dis));
	priority_queue<node> q;
	q.push(node(S,0));
	dis[S] = 0;
	while(q.size()){
		node t = q.top();
		q.pop();
		if(vis[t.x]) continue;
		vis[t.x] = true;
		for(int i = head[t.x];~i;i = nxt[i]) {
			int y = to[i],W = val[i];
			if(dis[y] > dis[t.x] + W) {
				dis[y] = dis[t.x] + W;
				if(!vis[y]) q.push(node(y,dis[y]));
			}
		}
	}
	
}

signed main() {
	init();
	n = rd(),m = rd();
	S = 0;E = (m - 1) * (n - 1) * 2 + 2;
	int ans = 0;
	for(int i = 1;i<=n;i++) 
		for(int j = 1;j<m;j++) 
			a[i][j] = rd();
	for(int i = 1;i<n;i++)
		for(int j = 1;j<=m;j++) 
			b[i][j] = rd();
	for(int i = 1;i<n;i++)
		for(int j = 1;j<m;j++)
			c[i][j] = rd();
	for(int i = 1;i<m;i++){
		add(2 * i,E,a[1][i]),add(E,2 * i,a[1][i]);
	}
	int k = (m - 1) * 2 * (n - 2);
	for(int i = 1;i<m;i++) {
		add(S,k + (2 * i) - 1,a[n][i]),add(k + (2 * i) - 1,S,a[n][i]);	
	}
	for(int i = 1;i<n;i++){
		k = (m - 1) * 2 * (i - 1) + 1;
		add(S,k,b[i][1]);add(k,S,b[i][1]);
	}
	for(int i = 2;i<=n;i++) {
		k = (m - 1) * 2 * (i - 1);
		add(k,E,b[i - 1][m]);add(E,k,b[i - 1][m]);
	}
	int layer = (m - 1) * 2;
	for(int i = 2;i<n;i++)
		for(int j = 1;j<m;j++) {
			int s = (i - 2) * layer + (j - 1) * 2 + 1;
			int e = (i - 1) * layer + j * 2;
			add(s,e,a[i][j]);add(e,s,a[i][j]);
		}

	for(int i = 1;i<n;i++) {
		for(int j = 2;j<m;j++) {
			int s = (i - 1) * layer + (j - 1) * 2;
			int e = s + 1;
			add(s,e,b[i][j]);add(e,s,b[i][j]);
		}
	}
	
	for(int i = 1;i<n;i++)	
		for(int j = 1;j<m;j++) {
			int s = (i - 1) * layer + (j - 1) * 2 + 1;
			int e = s + 1;
			add(s,e,c[i][j]);add(e,s,c[i][j]);
		}
	dij();

	wt(dis[E]);

	return 0;
}

2024/07/23#

梦熊题单#

E.基础数据结构题#

题目描述

给定一个长度为 n 的序列 A ,下标从 1 开始,你需要维护三种操作:

1{\kern 4pt}l{\kern 4pt}r{\kern 4pt}x: 对于 l \leq i \leq r ,令 A_i=A_i+x

2{\kern 4pt}l{\kern 4pt}r: 对于 l \leq i \leq r ,令 A_i=\lfloor \sqrt{A_i} \rfloor

3{\kern 4pt}l{\kern 4pt}r: 对于 l \leq i \leq r ,求 A_i 的和。

本题来自 $UOJ\ #228$

这里是李欣隆的课件:

sqrt这个操作肯定是一个均摊,因为下降很快
但是有区间加,怎么办呢
想一想感觉可以维护值相同的连续段试试?

带修的势能线段树!

我们要发现,当区间每一个值都相等时 \(x \rightarrow \sqrt{x} \Rightarrow x + (sqrt(x) - x)\)
那么就把区间根号的修改变成了区间加
然后就结束了......吗?

我们发现这样肯定有毛病

然后就TLE了
发现会被奇怪的数据卡
比如3 4 3 4 3 4
开sqrt后变成1 2 1 2 1 2
然后加2又变成3 4 3 4 3 4
Oh no!

发现这种情况仅当 \(a=b-1\)\(b\) 是完全平方数的时候会出现
于是想办法维护一下区间极差就可以判掉这种情况
由于取sqrt的次数是 \(O( \log\log v )\)
所以总复杂度是 \(O( (n+m)\log n \log \log v )\)
大概是使用所有连续段以外相邻位置的差来作为势能的均摊

AC-code(没有维护 \(a = b - 1\) 的情况,因为数据范围太小了,无论如何构造都能过原题):

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e5+5; 

int n,m,a[N];

namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

int t[N<<2],tag[N<<2],mx[N<<2],mn[N<<2],ts[N<<2];

void push_up(int p) {
    t[p] = t[ls] + t[rs];
    mx[p] = max(mx[ls],mx[rs]);
    mn[p] = min(mn[ls],mn[rs]);
}

void build(int p,int pl,int pr) {
    if(pl == pr) {t[p] = mx[p] = mx[p] = a[pl]; return;}
    build(ls,pl,mid);
    build(rs,mid+1,pr);
    push_up(p);
}

void addtag(int p,int pl,int pr,int k) {
    t[p] += (pr - pl + 1) * k;
    tag[p] += k;
    mx[p] += k;
    mn[p] += k;
} 

void push_down(int p,int pl,int pr) {
    if(tag[p]) {
        addtag(ls,pl,mid,tag[p]);
        addtag(rs,mid+1,pr,tag[p]);
        tag[p] = 0;
    }
}

void update(int p,int pl,int pr,int l,int r,int d) {
    if(l <= pl && pr <= r) {addtag(p,pl,pr,d);return;}
    push_down(p,pl,pr);
    if(l <= mid) update(ls,pl,mid,l,r,d);
    if(r > mid) update(rs,mid+1,pr,l,r,d);
    push_up(p);
}

void getsqrt(int p,int pl,int pr,int l,int r) {
    if(pl == pr) {mx[p] = mn[p] = t[p] = sqrt(t[p]);return;}
    int maxx = (int)sqrt(mx[p]) - mx[p],minn = (int)sqrt(mn[p]) - mn[p];//维护值相同的连续段
    if(l <= pl && pr <= r && (maxx == minn || (maxx - minn >= 1 && maxx == (int)sqrt(maxx) * (int)sqrt(maxx)))) {addtag(p,pl,pr,maxx);return;}
    push_down(p,pl,pr);
    if(l <= mid) getsqrt(ls,pl,mid,l,r);
    if(r > mid) getsqrt(rs,mid+1,pr,l,r);
    push_up(p);
}

int query(int p,int pl,int pr,int l,int r) {
    if(l <= pl && pr <= r) return t[p];
    push_down(p,pl,pr);
    int res = 0;
    if(l <= mid) res += query(ls,pl,mid,l,r);
    if(r > mid) res += query(rs,mid+1,pr,l,r);
    return res;
}

}

signed main() {

	n = rd(),m = rd();
    for(int i = 1;i<=n;i++) a[i] = rd();
    sgt::build(1,1,n);
    while(m--) {
        int opt = rd();
        int l,r,x;
        switch (opt)
        {
        case 1:
            l = rd(),r = rd(),x = rd();
            sgt::update(1,1,n,l,r,x);
            break;
        case 2:
            l = rd(),r = rd();
            sgt::getsqrt(1,1,n,l,r);
            break;
        case 3:
            l = rd(),r = rd();
            wt(sgt::query(1,1,n,l,r));
            putchar('\n');
            break;
        default:
            break;
        }
    }

	return 0;
}

求助 Chatgpt 的 SOS-dp 帮助:#

算法竞赛中 SOS 是什么?

在算法竞赛中,SOS 通常指的是 "Sum Over Subsets" 技术。SOS 技术是一种用于高效计算涉及子集的问题的动态规划方法,特别是在处理诸如子集>和、子集数目等方面的运算时非常有用。

SOS 动态规划的基本思想

假设你有一个大小为 ($ n $) 的集合 \(( \{0, 1, 2, \ldots, n-1\} )\),我们定义一个函数 ( f(S) ),其中 ( S ) 是该集合的一个子集。>我们的目标是计算每个子集 ( S ) 的某种函数值的和,这种和可以表示为:

$g(S) = \sum_{T \subseteq S} f(T) $

基本例子

假设我们有一个数组 ( f ),其中每个元素 ( f[S] ) 表示子集 ( S ) 的值。我们希望计算每个子集 ( S ) 的所有子集的值的和 ( g(S) >)。

递推公式

我们可以使用以下递推公式来计算 ( g(S) ):

$ g(S) = \sum_{T \subseteq S} f(T) $

通过动态规划,我们可以通过处理每个子集 ( S ) 以及其补集 ( \overline{S} ) 中的每一位来高效地计算 ( g(S) )。

实现步骤
  1. 初始化:定义一个数组 ( g ) 来存储结果,并初始化 ( g[S] = f[S] )。
  2. 迭代处理每一位:对于每一个可能的子集,迭代处理每一位。如果某一位在当前子集 ( S ) 中是未设置的(即该位为 0),则我们将其设置>为 1,并更新相应的 ( g ) 值。
示例代码

以下是用 C++ 实现 SOS 动态规划的示例代码:

#include <iostream>
#include <vector>
using namespace std;

void sos_dp(vector<int>& f, int n) {
    vector<int> g = f;

    for (int i = 0; i < n; ++i) {
        for (int mask = 0; mask < (1 << n); ++mask) {
            if (mask & (1 << i)) {
                g[mask] += g[mask ^ (1 << i)];
            }
        }
    }

    for (int mask = 0; mask < (1 << n); ++mask) {
        cout << "g[" << mask << "] = " << g[mask] << endl;
    }
}

int main() {
    int n = 3;
    vector<int> f(1 << n);

    // Define your f function here, e.g., f(S) = S
    for (int i = 0; i < (1 << n); ++i) {
        f[i] = i;  // Example function: f(S) = S
    }

    sos_dp(f, n);

    return 0;
}
解释
  1. 初始化g 数组初始化为 f
  2. 迭代处理每一位
  • 遍历每一位 (i)。
  • 对于每个可能的子集 mask,如果第 (i) 位在 mask 中设置(即 mask \& (1 << i) 为真),则更新 g[mask] 的值。

最终,g 数组将包含每个子集 (S) 的所有子集的和。

复杂度

这个算法的时间复杂度为 \((O(n \cdot 2^n))\),其中 (n) 是集合的大小。这是因为我们需要处理每个子集,并且对每一位进行更新。尽管看起来复>杂,但对于小规模的问题(如 (\(n \leq 20\))),这是一个非常高效的算法。

2024/07/24#

A. 最短路(package)#

题目描述

给定一个包含 n 个节点和 m 条边的图,每条边有一个权值。

你的任务是回答 k 个询问,每个询问包含两个正整数 st 表示起点和终点,要求寻找从 st 的一条路径,使得路径上权值最大的一条边权值最小。

接下来 m 行,每行三个整数 u , v , w , 表示一个由 uv 的长度为 w 的双向边。

再接下来 k 行,每行两个整数 s , t ,表示询问从 s 连接到 t 的所有路径中单边长度最大值的最小值。

另外,如果 st 没有路径相连通,输出 -1 即可。

8 11 3
1 2 10
2 5 50
3 4 60
7 5 60
3 6 30
1 5 30
6 7 20
1 7 70
2 3 20
3 5 40
2 6 90
1 7
2 8
6 2
30
-1
30

此题写全源最短路? \(n \leq 1000,m \leq 10000,k \leq 1000\) 不允许

那么我们就得换一种方式思考,最小值 & 连通

请想到 最小生成树,这就是答案!

我们对每一颗连通块建最小生成树(类似与 货车运输

如何对每一次询问,判断是否在同一颗生成树,然后暴力求 \(s \rightsquigarrow t\) 的路径上的最大值

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e3+5,M = 1e5+5;

int n,m,k;
struct Edge{
	int u,v,w;
}edge[M<<1];

int s[N<<1],cnt;
bool cmp(const Edge &a,const Edge &b) { return a.w < b.w;}

int find(int x){
	if(x != s[x]) s[x] = find(s[x]);
	return s[x];
}

int head[N],nxt[M<<1],to[M<<1],idx,val[M<<1];

void init(){memset(head,-1,sizeof(head));}

void add(int u,int v,int w) {
    nxt[idx] = head[u];
    to[idx] = v;val[idx] = w;
    head[u] = idx++;
}

void kru(){
	sort(edge+1,edge+1+m,cmp);
	int f1,f2;
	for(int i = 1;i<=n;i++) s[i] = i;
	for(int i = 1;i<=m;i++) {
		f1 = find(edge[i].u);
		f2 = find(edge[i].v);
		if(f1 != f2) {
            add(edge[i].u,edge[i].v,edge[i].w);
            add(edge[i].v,edge[i].u,edge[i].w);
			s[f1] = f2;
			cnt++;
			if(cnt == n-1) break;
		}
	}
}

int dep[N],e[N],fa[N];

void dfs1(int x,int f) {
    dep[x] = dep[f] + 1;
    fa[x] = f;
    for(int i = head[x];~i;i = nxt[i]) {
        int y = to[i];
        if(y ^ f) {
            e[y] = val[i];
            dfs1(y,x);
        }
    }
}

int find_path(int s,int t) {
    int ans = 0;
    while(s != t) {
        if(dep[s] <= dep[t]) swap(s,t);
        ans = max(ans,e[s]);
        s = fa[s];
    }
    return ans;
}

signed main() {
	init();
    n = rd(),m = rd(),k = rd();
    for(int i = 1;i<=m;i++) {
        int u = rd(),v = rd(),w = rd();
        edge[i].u = u;
        edge[i].v = v;
        edge[i].w = w;
    }

    kru();

    for(int i = 1;i<=n;i++) if(!dep[i]) dfs1(i,0);

    while(k--) {
        int s = rd(),t = rd();
        if(find(s) ^ find(t)) {
            wt(-1);putchar('\n');
            continue;
        }
        wt(find_path(s,t));
        putchar('\n');
    }
	return 0;
}

Lomsat gelral#

线段树合并练习题

我们怎么解决问题呢?

寻找众数,自然是 权值线段树 & 线段树上二分

对每一个节点,构造一颗权值线段树,先将本节点权值添加到本节点线段树上

然后将子树的线段树都合并到本节点上(以一种从下到上的合并对每一个节点进行合并)

然后进行查询答案(因为查询是全局的,所以只有查询根节点就可以了)

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e5+5;

int head[N],nxt[N<<1],to[N<<1],cnt;

void init(){memset(head,-1,sizeof(head));}

void add(int u,int v) {
	nxt[cnt] = head[u];
	to[cnt] = v;
	head[u] = cnt++;
}

int n,a[N],root[N];

int ls[N * 50],rs[N * 50],ans[N],sum[N * 50],typ[N * 50],A[N * 50],idx;

#define mid ((pl + pr) >> 1)

void push_up(int p) {
	if(sum[ls[p]] < sum[rs[p]]) {
		sum[p] = sum[rs[p]];
		typ[p] = typ[rs[p]];
		A[p] = A[rs[p]];
	}else if(sum[ls[p]] > sum[rs[p]]) {
		sum[p] = sum[ls[p]];
		typ[p] = typ[ls[p]];
		A[p] = A[ls[p]];
	}else{
		sum[p] = sum[ls[p]];
		typ[p] = typ[ls[p]];
		A[p] = A[ls[p]] + A[rs[p]];
	}
}

void update(int &p,int pl,int pr,int k) {
	if(!p) p = ++idx;
	if(pl == pr) {
		sum[p]++;
		typ[p] = k;
		A[p] = k;
		return;
	}
	if(k <= mid) update(ls[p],pl,mid,k);
	else update(rs[p],mid+1,pr,k);
	push_up(p);
}

int merge(int x,int y,int pl,int pr) {
	if(!x || !y) return x + y;
	if(pl == pr) {sum[x] += sum[y];return x;}
	ls[x] = merge(ls[x],ls[y],pl,mid);
	rs[x] = merge(rs[x],rs[y],mid+1,pr);
	push_up(x);
	return x;
}

void dfs(int x,int f) {
	update(root[x],1,N,a[x]);
	for(int i = head[x];~i;i = nxt[i]) {
		int y = to[i];
		if(y ^ f){
			dfs(y,x);
			root[x] = merge(root[x],root[y],1,N);
		}
	}
	ans[x] = A[root[x]];
}

signed main() {
	
	init();
	n = rd();
	for(int i = 1;i<=n;i++) a[i] = rd();
	for(int i = 1;i<n;i++) {
		int u = rd(),v = rd();
		add(u,v);add(v,u);
	}
	dfs(1,0);
	for(int i = 1;i<=n;i++) wt(ans[i]),putchar(' ');

	return 0;
}

2024/07/26#

Tourists#

题面:

Tourists
题面翻译

Cyberland 有 \(n\) 座城市,编号从 \(1\)\(n\),有 \(m\) 条双向道路连接这些城市。第 \(j\) 条路连接城市 \(a_j\)\(b_j\)。每天,都有成千上万的游客来到 Cyberland 游玩。

在每一个城市,都有纪念品售卖,第 \(i\) 个城市售价为 \(w_i\)。这个售价有时会变动。

每一个游客的游览路径都有固定起始城市和终止城市,且不会经过重复的城市。

他们会在路径上的城市中,售价最低的那个城市购买纪念品。

你能求出每一个游客在所有合法的路径中能购买的最低售价是多少吗?

你要处理 \(q\) 个操作:

C a w: 表示 \(a\) 城市的纪念品售价变成 \(w\)

A a b: 表示有一个游客要从 \(a\) 城市到 \(b\) 城市,你要回答在所有他的旅行路径中最低售价的最低可能值。

输入格式

第一行包含用一个空格隔开的三个数 \(n, m, q\)

接下来 \(n\) 行,每行包含一个数 \(w_i\)

接下来 \(m\) 行,每行包含用一个空格隔开的两个数 \(a_j\),\(b_j\)。(\(1 \le a _ j, b _ j \le n,a _ j \neq b _ j\)

数据保证没有两条道路连接同样一对城市,也没有一条道路两端是相同的城市。并且任意两个城市都可以相互到达。

接下来 \(q\) 行,每行是 C a wA a b ,描述了一个操作。

输出格式

对于每一个 A 类操作,输出一行表示对应的答案。

样例 #1
样例输入 #1
3 3 3
1
2
3
1 2
2 3
1 3
A 2 3
C 1 5
A 2 3
样例输出 #1
1
2
样例 #2
样例输入 #2
7 9 4
1
2
3
4
5
6
7
1 2
2 5
1 5
2 3
3 4
2 4
5 6
6 7
5 7
A 2 3
A 6 4
A 6 7
A 3 3
样例输出 #2
2
1
5
3

很好很好的 圆方树 + 树剖

因为对于每一个点双,从任意点出发、任意点结束,都可以找到路径经过指定的点

自然可以想到建圆方树,然后对圆方树树剖

那么,我们只要维护点双中值最小的点即可

修改操作,我们使用multiset用平衡树自动维护最小值,更新时删被修改值,添加更新值和方点值

可以将点双中值最小的点存进方点vector<int>中,然后树剖查路径最小值

然后,我们惊讶的发现 TLE

因为我们会被菊花图卡爆成 \(O(n^2)\)

我们更自然的观察圆方树

img

不难发现 如 \(2 \sim 3\) 的路径,需要访问 节点 \(1\) ,而这种节点有且仅有一个,是方点的父亲,如:

img

那么我们只需要在询问时对方点的父亲特别查询一下就好了!

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int long long

int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 3e5+5,inf = 1e18;

struct Edge{
int head[N << 1],nxt[N<<1],to[N<<1],cnt;

Edge() {memset(head,-1,sizeof(head));}

void add(int u,int v) {
	nxt[cnt] = head[u];
	to[cnt] = v;
	head[u] = cnt++;
}

}g,G;

int n,m,q,w[N];
int dfn[N],low[N],idx,ID;
int st[N],top;

multiset<int> s[N];

void tarjan(int x) {
	low[x] = dfn[x] = ++idx;
	st[++top] = x;
	for(int i = g.head[x];~i;i = g.nxt[i]) {
		int y = g.to[i];
		if(!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x],low[y]);
			if(dfn[x] <= low[y]) {
				ID++;
				int v;
				do{
					v = st[top--];
					G.add(v,ID);
					G.add(ID,v);
				}while(v ^ y);
				G.add(ID,x);
				G.add(x,ID);
			}
		}else low[x] = min(dfn[y],low[x]);
	}
}

int fa[N],dep[N],id[N],_w[N],siz[N],son[N],num,_top[N],_id[N];

void dfs1(int x,int f) {
	dep[x] = dep[f] + 1;
	siz[x] = 1;
	fa[x] = f;
	for(int i = G.head[x];~i;i = G.nxt[i]) {
		int y = G.to[i];
		if(y ^ f) {
			dfs1(y,x);
			siz[x] += siz[y];
			if(siz[son[x]] < siz[y]) son[x] = y;
		}
	}
}

void dfs2(int x,int topx) {
	_top[x] = topx;
	id[x] = ++num;
	_id[num] = x;
	if(!son[x]) return;
	dfs2(son[x],topx);
	for(int i = G.head[x];~i;i = G.nxt[i]) {
		int y = G.to[i];
		if(y ^ fa[x] && y ^ son[x]) 
			dfs2(y,y);
	}
}

namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

int t[N<<2];

void push_up(int p) {t[p] = min(t[ls],t[rs]);}

void build(int p,int pl,int pr) {
	if(pl == pr) {
		t[p] = w[_id[pl]];
		return;
	}
	build(ls,pl,mid);
	build(rs,mid+1,pr);
	push_up(p);
}

void update(int p,int pl,int pr,int k,int d) {
	if(pl == pr) {
		t[p] = d;
		return;
	}
	if(k <= mid) update(ls,pl,mid,k,d);
	if(k > mid) update(rs,mid+1,pr,k,d);
	push_up(p);
}

int query(int p,int pl,int pr,int l,int r) {
	if(l <= pl && pr <= r) return t[p];
	int res = inf;
	if(l <= mid) res = min(res,query(ls,pl,mid,l,r));
	if(r > mid) res = min(res,query(rs,mid+1,pr,l,r));
	return res;
}
}

int query(int x,int y) {
	int res = inf;
	while(_top[x] != _top[y]) {
		if(dep[_top[x]] < dep[_top[y]]) swap(x,y);
		res = min(res,sgt::query(1,1,num,id[_top[x]],id[x]));
		x = fa[_top[x]];
	}
	if(dep[x] > dep[y]) swap(x,y);
	res = min(res,sgt::query(1,1,num,id[x],id[y]));
	if(x > n) res = min(res,sgt::query(1,1,num,id[fa[x]],id[fa[x]]));
	return res;
}

void update(int x,int d) {
	sgt::update(1,1,num,id[x],d);
	if(fa[x]) {
		s[fa[x]].erase(s[fa[x]].lower_bound(w[x]));
		s[fa[x]].emplace(d);
			if(w[fa[x]] != *s[fa[x]].begin()) 
				w[fa[x]] = *s[fa[x]].begin();	
			sgt::update(1,1,num,id[fa[x]],w[fa[x]]);
	}
	w[x] = d;
}


signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	
	cin>>n>>m>>q;
	ID = n;
	for(int i = 1;i<=n;i++) {
		cin>>w[i];
		s[i].emplace(w[i]);
	}
	for(int i = 1;i<=m;i++) {
		int u,v;
		cin>>u>>v;
		g.add(u,v);g.add(v,u);
	}
	
	tarjan(1);
	dfs1(1,0);
	dfs2(1,1);
	for(int i = 1;i<=n;i++) 
		if(fa[i])
			s[fa[i]].emplace(w[i]);
	for(int i = n + 1;i<=ID;i++) 
	 	w[i] = *s[i].begin();
	
	sgt::build(1,1,num);

	while(q--) {
		char opt;int a,b;
		cin>> opt >> a >> b;
		switch(opt) {
		case 'C':
			update(a,b);
			break;
		case 'A': 
			cout<<query(a,b)<<'\n';
			break;
		}
	}


	return 0;
}

2024/07/28#

P3792 由乃与大母神原型和偶像崇拜#

因为题面太长就不放了 真有你的 lxl

假做法:维护立方和,平方和,和,最大值,最小值,然后逐一判断

因为没有用 \(hash\) 和 自然溢出,用了 \(__int128\) 导致卡了会常

AC-code:

#include<bits/stdc++.h>
using namespace std;

#define int __int128

struct FastIO
{
#define get( ) getchar( )
#define put(x) putchar(x)
public:
	inline FastIO &operator >>(char &t)  { t = get(); return *this; }
	inline FastIO &operator >>(char *t)  { while((*t = get()) != '\n') *(++t) = '\0'; return *this; }
	template <typename type>
	inline FastIO &operator >>(type &x)  { x = 0; register int sig = 1; register char ch = get();
		while (ch < 48 || ch > 57) { if (ch == '-') sig = -1; ch = get(); }
		while (ch > 47 && ch < 58) x = (x << 3) + (x << 1) + (ch ^ 48),
		ch = get(); x *= sig; return *this; }
	template <typename type>
	inline FastIO &operator <<(type  x)  { if (!x) put('0'); if (x < 0) put('-'), x = -x; static char vec[50];
		register int len = 0; while (x) vec[len++] = x % 10 + '0', x /= 10;
		while (len--) put(vec[len]); return *this; }
	template <typename type>
	inline FastIO &operator <<(type *t)  { for (; *t; t++) put(*t); return *this; }
	inline FastIO &operator <<(char  t)  { put(t); return *this; }
}IO;

const int N = 500005,inf = 1e18;

int n,m,a[N];

#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

int s[N<<2],s2[N<<2],s3[N<<2],mx[N<<2],mn[N<<2];

inline void push_up(int p) {
    s[p] = s[ls] + s[rs];
    s2[p] = s2[ls] + s2[rs];
    s3[p] = s3[ls] + s3[rs];
    mx[p] = max(mx[ls],mx[rs]);
    mn[p] = min(mn[ls],mn[rs]);
}

inline void build(int p,int pl,int pr) {
    if(pl == pr) {
        mn[p] = mx[p] = s[p] = a[pl];
        s2[p] = a[pl] * a[pl];
        s3[p] = a[pl] * a[pl] * a[pl];
        return;
    }
    build(ls,pl,mid);
    build(rs,mid+1,pr);
    push_up(p);
}

inline void update(int p,int pl,int pr,int k,int d) {
    if(pl == pr) {
        mn[p] = mx[p] = s[p] = d;
        s2[p] = d * d;
        s3[p] = d * d * d;
        return;
    }
    if(k <= mid) update(ls,pl,mid,k,d);
    else update(rs,mid+1,pr,k,d);
    push_up(p);
}

inline array<int,3> query(int p,int pl,int pr,int l,int r) {
    if(l <= pl && pr <= r) return array<int,3>{s[p],s2[p],s3[p]};
    if(r <= mid) return query(ls,pl,mid,l,r);
    else if(l > mid) return query(rs,mid+1,pr,l,r); 
    else  {
        array<int,3> a = query(ls,pl,mid,l,r),b = query(rs,mid+1,pr,l,r);
        array<int,3> c = array<int,3>{a[0] + b[0],a[1] + b[1],a[2] + b[2]};
        return c;
    }
}

inline int query_min(int p,int pl,int pr,int l,int r) {
    if(l <= pl && pr <= r) return mn[p];
    int res = inf;
    if(l <= mid) res = min(res,query_min(ls,pl,mid,l,r));
    if(r > mid) res = min(res,query_min(rs,mid+1,pr,l,r));
    return res;
}

inline int query_max(int p,int pl,int pr,int l,int r) {
    if(l <= pl && pr <= r) return mx[p];
    int res = -inf;
    if(l <= mid) res = max(res,query_max(ls,pl,mid,l,r));
    if(r > mid) res = max(res,query_max(rs,mid+1,pr,l,r));
    return res;
}

signed main() {

    IO>>n>>m;
    for(int i = 1;i<=n;i++) 
    	IO>>a[i];
    build(1,1,n);
    array<int,3> A;
	int opt,x,y,l,r;
	bool flag;
    while(m--) {
        IO>>opt>>x>>y;
        if(opt == 1) update(1,1,n,x,y);
        else {
            A = query(1,1,n,x,y);
            l = query_min(1,1,n,x,y),r = query_max(1,1,n,x,y);
            flag = true;
            if(A[0] != (r + 1) * r / 2 - (l - 1) * l / 2) flag = false;
            if(A[1] != r * (r + 1) * (2 * r + 1) / 6 - (l - 1) * l * (2 * l - 1) / 6) flag = false;
            if(A[2] != ((r + 1) * r / 2) * ((r + 1) * r / 2) - ((l - 1) * l / 2) * ((l - 1) * l / 2)) flag = false;
            if(flag) puts("damushen");
            else puts("yuanxing");
		}
    }
	return 0;
}

2024/07/30#

梦熊训练赛#

D.Max and Mex#

题目描述

给定三个整数 n,c,d ,和一棵 n 个点的树。

树的每个点有点权 a_i ,保证点权两两不同。

对于树的一条链,设其点权集合的 mexx ,点权集合的 maxy ,则这条链的价值为 cx+dy

求出价值最大的链的价值。

注:集合的 mex 定义为最小的未在该集合中出现过的非负整数。

输入格式

第一行三个整数 n,c,d

第二行 n 个整数表示 a_i

接下来的 n-1 行,每行两个整数表示一条树边。

输出格式

一行一个整数表示答案。

样例
输入样例1
5 2 3
0 1 2 3 4
1 2
1 3
2 4
2 5
输出样例1
18
输入样例2
20 1 1
10 18 13 8 19 15 0 14 11 17 12 5 4 9
16 2 1 6 7 3
17 9
6 17
20 17
4 17
18 9
15 17
2 18
13 2
3 20
12 15
10 18
5 3
11 20
7 13
16 12
1 12
8 15
19 11
14 7
输出样例2
21
数据范围与提示

2 \leq n \leq 10^5

0 \leq c,d \leq 10^9

0 \leq a_i \leq n-1

保证 a_i 两两不同。

\(\max\) 太难控制了,但是 \(mex\) 很好控制,因为每个节点的权值互不相同

我们可以通过 \(dfs\) 枚举

然后在路径两端和路径上找 \(\max\)

AC-code(但没过CF,应该是扩展链没写对但我不想改辣):

#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}
void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}
const int N = 1e5+5;
int n,c,d,a[N],ans;
int head[N],nxt[N<<1],to[N<<1],cnt;
void init() {memset(head,-1,sizeof(head));}
void add(int u,int v){
    nxt[cnt] = head[u];
    to[cnt] = v;
    head[u] = cnt++;
}
int fa[N],top[N],num,dep[N],siz[N],son[N],pos[N],id[N],w[N];
void dfs1(int x,int f) {
    fa[x] = f;
    dep[x] = dep[f] + 1;
    siz[x] = 1;
    for(int i = head[x];~i;i = nxt[i]) {
        int y = to[i];
        if(y ^ f) {
            dfs1(y,x);
            siz[x] += siz[y];
            if(siz[son[x]] < siz[y]) son[x] = y;
        }
    }
}
void dfs2(int x,int topx) {
    top[x] = topx;
    id[x] = ++num;
    w[num] = a[x];
    if(!son[x]) return;
    dfs2(son[x],topx);
    for(int i = head[x];~i;i = nxt[i]) {
        int y = to[i];
        if(y ^ fa[x] && y ^ son[x]) dfs2(y,y);
    }
}
int lca(int x,int y) {
    while(top[x] !=  top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    return x;
}

namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

int mx[N<<2];

void push_up(int p) {mx[p] = max(mx[ls],mx[rs]);}

void build(int p,int pl,int pr) {
    if(pl == pr) {mx[p] = w[pl];return;}
    build(ls,pl,mid);
    build(rs,mid+1,pr);
    push_up(p);
}

int query(int p,int pl,int pr,int l,int r) {
    if(r < l) return 0;
    if(l <= pl && pr <= r) return mx[p];
    int res = 0;
    if(l <= mid) res = max(res,query(ls,pl,mid,l,r));
    if(r > mid) res = max(res,query(rs,mid+1,pr,l,r));
    return res;
}
}

int query(int x,int y) {
    if(id[x] > id[y]) return 0;
    int r = 0;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        r = max(r,sgt::query(1,1,num,id[top[x]],id[x]));
        x = fa[top[x]];
    }   
    if(dep[x] > dep[y]) swap(x,y);
    r = max(r,sgt::query(1,1,num,id[x],id[y]));
    return r;
}

int query(int x) {return sgt::query(1,1,num,id[x],id[x] + siz[x] - 1);}

void solve(int D,int x,int y){
	if(dep[x] > dep[y]) swap(x,y); 
    int LCA = lca(x,y);
    int r = query(x,y);
    if(LCA == x) {
        for(int i = head[x];~i;i = nxt[i]) {
            int v = to[i];
            if(fa[x] ^ v) 
                if(lca(y,v) == v){
                    r = max(r,max(sgt::query(1,1,num,1,id[v]-1),sgt::query(1,1,num,id[v] + siz[v],num)));
                    r = max(r,query(y));
                    ans = max(ans,c * (D + 1) + d * r);
                }
        }
    }else {
        r = max(query(x),r);
        r = max(query(y),r);
        ans = max(ans,c * (D + 1) + d * r);
    }
    int nxtPos = pos[D + 1];
    int lt = lca(x,nxtPos),rt = lca(y,nxtPos);
    if(lt == nxtPos || rt == nxtPos)
    	solve(D + 1,x,y);
    else if(lt == x && rt == y) 
    	solve(D + 1,x,nxtPos);
    else if(lt == x && rt != y) {
    	if(LCA == x)
    		for(int i = head[x];~i;i = nxt[i]) {
    			int v = to[i];
    			if(v ^ fa[x] && lca(v,y) == v && lca(nxtPos,v) == v)
    				return;
			}
    	solve(D + 1,nxtPos,y);
	} 	
}

signed main() {
    init();
    n = rd(),c = rd(),d = rd();
    for(int i = 1;i<=n;i++) a[i] = rd(),pos[a[i]] = i;
    for(int i = 1,u,v;i<n;i++) {
        u = rd(),v = rd();
        add(u,v);add(v,u);
    }
    dfs1(pos[0],0);
    dfs2(pos[0],pos[0]);
    sgt::build(1,1,num);
    int _max = 0;
    for(int i = 1;i<=n;i++) _max = max(_max,a[i]);
    ans = _max * d + c;
    solve(1,pos[0],pos[1]);
    wt(ans);
	return 0;
}

\(7\) 月结束力,接下来是 永无止境的八月

作者:MingJunYi

出处:https://www.cnblogs.com/WG-MingJunYi/p/18278649

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   MingJunYi  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示