Logo __vector__ 的博客

博客

标签
暂无

CF2124E 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-07-10 12:40:53

被这题消耗了 5h 时间,记录一下。

首先,令 $pre_i$ 表示 $\sum\limits_{j=1}^ia_j$,$suf_i$ 表示 $\sum\limits_{j=i}^na_j$,$sum$ 表示 $\sum\limits_{i=1}^na_i$。

首先,考虑无解条件怎么判定。

  1. 注意到,每次操作减去的总和必然是偶数。

    所以,总和必须是偶数。

  2. 最大值不能超过总和的一半。

    这是因为,一个数减去 $1$,必然至少有一个其他的数也得同时减去 $1$。

以上两种必然无解,其他情况还不知道。

先把无解情况判掉,再考虑剩余情况怎么处理。

注意到,所有 $n \gt 3$ 的情况都可以转化为 $n=3$ 的情况,且不会转为上述无解情况,可以这样:

  1. 从前到后扫描,找到最大的 $x$,满足 $pre_x \le suf_{x+1}$:

    此时,可以得到性质 $pre_x \le \frac{sum}{2}$,$suf_{x+2} \le \frac{sum}{2}$。

    另外,$x \lt n-1$,否则就是无解情况(之前已经判掉了)。

  2. 划分为 $3$ 段,$[1,x],[x+1,x+1],[x+2,n]$:

    这 $3$ 段的总和分别相当于新的 $a_1,a_2,a_3$,操作时将它们分别视为一个整体操作就可以了。

接下来,只需要分类讨论 $n=2,3$ 的情况了。

  • $n=2$ 的情况:

    注意到,此时必然满足 $a_1=a_2$,操作一次 $b_1=b_2=a_1$ 就可以了。

  • $n=3$ 的情况:

    如果想要一次操作结束,只能是以下两种情况:

    1. $a_1+a_2 = a_3$。

    2. $a_1=a_2+a_3$。

    现在,考虑其他的情况怎么操作。

    • 转化为 $n=2$,且两个数字相等的情况:

      • 变为 $a_1=a_2$ 的情况:

        前提条件是 $a_2 - a_1 \le a_3$ 且 $a_2-a_1 \equiv a_3\pmod 2$。

        不过,最容易想到的一种条件是 $a_2 - a_3 = a_1$。

      • 变为 $a_2=a_3$ 的情况:

        前提条件是 $a_2 - a_3 \le a_1$ 且 $a_2 - a_3 \equiv a_1 \pmod 2$。

        最容易想到的条件仍然是 $a_2-a_3=a_1$。

      • 变为 $a_1=a_3$ 的情况:

        • 若 $a_1 \lt a_3$:

          那么,操作 $b_2=a_2,b_3=a_3-a_1$。

          也就是说,本种情况,能进行转化操作的前提是 $a_3-a_1 = a_2$。

          但是,这种情况的答案就是 $1$,使用本转化就得 $2$ 次了,不优。

          故,不考虑本情况。

        • 若 $a_1 \ge a_3$:

          那么,操作 $b_2 = a_2,b_1 = a_1 - a_3$。

          本种情况,转化前提是 $a_1-a_3 = a_2$。

          同样,本种情况答案是 $1$,使用本转化就得 $2$ 次了,不优。

          故,同样不考虑本情况。

      可以看出来,如果转为 $n=2$ 的情况,那么全局一定需要 $2$ 次操作。

    • 变为 $a_1+a_3 = a_2$ 的情况:

      此时,尝试求出一个 $d$,使得 $(a_1-d)+(a_3-d)=a_2$。

      推导可得,$d = \frac{a_1+a_3-a_2}{2}$。

      那么如何证明 $d$ 合法?

      $a_1+a_3-a_2 = a_1+a_2+a_3-2a_2$。

      由于 $a_1+a_2+a_3$ 为偶数,$a_1+a_3-a_2$ 也是偶数。

      所以 $d$ 是整数。

      然后需要证明 $d \le \min(a_1,a_3)$。

      $a_1 \ge \frac{a_1+a_3-a_2}{2}$ 可以转化为 $2a_1 \ge a_1 + a_3-a_2$,即 $a_1 \ge a_3-a_2$。

      如果上述条件不满足,即 $a_1 \lt a_3-a_2$,这就属于无解情况,已经判掉了,不存在。

      同样方法也可以证明 $a_3 \ge \frac{a_1+a_3-a_2}{2}$。

      由此可以构造出 $b_1=b_3=\frac{a_1+a_3-a_2}{2},b_2=0$。

      如果推导到这一步就结束了,那么全局需要 $3$ 次操作。

      但实际上,本情况只需要两次操作。

      • 考虑一下 $a_1+a_3=a_2$ 的情况。

        下一步转化为 $a_1=a_2$ 的情况,这就需要构造:

        $b_1=0,b_2=a_3,b_3=a_3$

      注意到,本次转化操作,和下一次转化操作其实可以合并为一次进行操作。

      新的构造:

      1. $b_1=\frac{a_1+a_3-a_2}{2}$。
      2. $b_2= 0 + (a_3-\frac{a_1+a_3-a_2}{2})$,括号里的数字代表 $a_3$ 第一次操作后的新值。
      3. $b_3 = \frac{a_1+a_3-a_2}{2} + (a_3-\frac{a_1+a_3-a_2}{2})=a_3$。

      这样,一次操作直接转化为了 $a_1=a_2$ 的情况,也就是最终全局 $2$ 次操作。

上述构造,说明了任意非无解情况都只需要最多两次操作。

这是一个比较丑陋的实现:

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
#define NO {puts("-1");return;}
const int maxn=2e5+5;
int n;
ll a[maxn],tot[4];
ll pre[maxn];
ll w(int l,int r){
	return pre[r]-pre[l-1];
}
int lef[4],rig[4];\/\/ 记录 a[1],a[2],a[3] 对应原数组实际范围
void op(ll* b,int len){
	FOR(id,1,len){\/\/ 目前减去 len 个中的第 id 个
		FOR(i,lef[id],rig[id]){
			ll _=min(a[i],b[id]);
			a[i]-=_;
			b[id]-=_;
			printf("%lld ",_);
		}
	}
	pln
}
void solve(int id_of_test){
	cin>>n;
	FOR(i,1,n){
		cin>>a[i];
		pre[i]=pre[i-1]+a[i];
	}
	ll sum=pre[n];
	if(sum%2)NO
	if((*max_element(a+1,a+n+1))>sum\/2)NO
	if(n>=4){
		FOR(i,1,n){
			if(w(1,i)>w(i+1,n)){
				lef[1]=1,rig[1]=i-1;
				lef[2]=i,rig[2]=i;
				lef[3]=i+1,rig[3]=n;
				tot[1]=w(1,i-1);
				tot[2]=w(i,i);
				tot[3]=w(i+1,n);
				break;
			}
		}
	}else{
		FOR(i,1,n)lef[i]=rig[i]=i,tot[i]=a[i];
	}
	if(n==2){
		puts("1");
		ll b[3]={-1,a[1],a[2]};
		op(b,2);
	}else{
		if(tot[1]+tot[2]==tot[3]||tot[1]==tot[2]+tot[3]){
			puts("1");
			ll b[4]={-1,tot[1],tot[2],tot[3]};
			op(b,3);
		}else{
			puts("2");
			ll b[4]={-1,(tot[1]+tot[3]-tot[2])\/2,tot[3]-(tot[1]+tot[3]-tot[2])\/2,tot[3]};
			op(b,3);
			b[1]=tot[1];
			b[2]=tot[2];
			b[3]=0;
			op(b,3);
		}
	}
}
int main()
{
	int T;
	cin>>T;
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}
\/*
1. 对题意的理解能否和样例对的上?
2. 每一步操作,能否和自己的想法对应上?
3. 每一步操作的正确性是否有保证?
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?
*\/

2025.8.6 比赛记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-08-06 22:10:42

Contest link

很遗憾没有 AK 比赛,原因在于 T4 我把 vector 当成了 set 用(排序是算法正确的基础之一),以及忘了处理取余结果为负数。

不过两位 hdu 队友以及 Cubber 都 AK 了,膜拜!!!!

T1

签到题。

注意到,推导最需要的信息包括 $a$ 的最大值和 $b$ 的总和,想要得到的信息是选择方案数。

首先,将所有 $(a_i,b_i)$ 按照 $a$ 升序排列。

令 $f_{i,j}$ 表示最大值不超过 $a_i$,且总和为 $j$ 的方案数。

$O(n^2)$ 暴力转移。

然后,最大值确定为 $i$,总和为 $j$ 的方案数则是 $f_{i,j}-f_{i-1,j}$。

然后暴力枚举最大值,总和,算出对应方案数总和。

时间复杂度 $O(n^2)$。

T2

和 T1 难度近似,但是存在一些误区会把一些比较幸运的人(比如我)引进去。

注意到,这实际上和括号匹配是类似的。

令 $f_{l,r}$ 表示 $[l,r]$ 区间内部完成匹配的操作方案数。

转移的话,则可以枚举 $r$ 匹配哪个位置,假设 $r$ 匹配位置 $x$,那么分别求解 $[l,x-1],[x+1,r-1]$ 的答案,然后将两个区间的答案合并。

两个区间的答案并不能简单的相乘,因为两个区间的操作顺序是互相独立的,每种不同的归并顺序都是一种新的方案。

两个区间实际上分别有 $\frac{x-l}{2},\frac{r-x}{2}$ 个元素,假设分别已经确定了顺序,每次可以在两个区间中任意选一个删掉,现在想要知道删完两个序列的方案数。

这个也是可以预处理的,$g_{i,j}$ 表示两个长度分别为 $i,j$ 的序列的归并方案数,$O(n^2)$ 暴力递推可以得到答案。

最终解法总复杂度为区间 DP $O(n^3)$ 加上预处理 $g$ 的 $O(n^2)$,即 $O(n^3)$。

T3

考虑一下什么时候最短路才会变化。

注意到,如果删除的边不在最短路上,那么一定不会改变最短路,可以直接跳过。

但是实际上可能有多条最短路,最短路上的边的数量同样可能到达 $O(n^2)$ 量级。

但是,注意到其实,只选择一条最短路,按住钦定其上的边为最短路上的边就可以了,因为只要有一条最短路仍然没断开,那么最短路长度仍然不会变化。

此时,只有 $O(n)$ 条边断开可能产生影响,每次 BFS $O(n^2)$ 计算新的最短路,可以通过。

T4

首先,观察一下最终走过的点的坐标形式都是什么。

令前 $n-1$ 次移动到达的点的位置集合(包括一开始的 $(0,0)$)为 $S$,第 $n$ 次移动后的点的位置为 $(dx,dy)$。

那么,对于所有 $Kn$ 次移动中到达过的点 $(x,y)$,必然满足以下两种情况至少一个:

  • 存在一个非负整数 $k \lt K$,以及 $(sx,sy) \in S$,使得 $(sx+k\cdot dx,sy+k\cdot dy) = (x,y)$
  • 存在一个非负整数 $k \le K$,使得 $(x,y) = (k\cdot dx,k\cdot dy)$

换个理解方式,就是对于任意 $(x,y) \in S,0 \le k \lt K$,$(x+k\cdot dx,y + k \cdot dy)$ 必然存在于最终经过的路径里,对于任意 $k \le K$,$(k\cdot dx,k \cdot dy)$ 也必然存在于最终经过的路径里。

首先,由于我不喜欢比较重要的地方出现负数,所以,如果 $dx,dy$ 某一个小于 $0$,那么就将整个的对应的横向或纵向翻转(比如说,U 换成 DL 换成 R)。

答案一定不会改变,因为是对称的,然后,就能保证 $dx,dy \ge 0$。

然后,考虑按照顺序遍历 $S$ 中的每个元素,依次考虑遍历到的新元素,事实上对答案新增了多少个位置的贡献。

此时,就需要分析怎么计算当前的元素产生的位置里面,有哪些是和之前遍历过的元素产生的位置重复了。

回想一下判定条件。

假设当前扫描到了 $S$ 中的元素 $(x_1,y_1)$,然后需要知道其与 $S$ 中另一个元素 $(x_2,y_2)$ 有哪些位置重合。

  • 首先,需要满足 $\frac{x_1-x_2}{dx} = \frac{y_1-y_2}{dy}$
    本条件的原因是,加上的 $dx,dy$ 分别乘上的倍数都是相等的。
    本条件可以转化为 $x_1\cdot dy - y_1 \cdot dx = x_2 \cdot dy - dx \cdot y_2$,这样等式两边分别都只包含来自同一个元素的值,以及差不多相当于常量的 $dx,dy$。
  • 其次,需要满足 $x_1 \equiv x_2 \pmod {dx},y_1 \equiv y_2 \pmod {dy}$
    如果不满足这个,那么当然是不能相等的,因为不能有小数。
  • 然后,需要满足 $|\frac{x_1-x_2}{d_x}| \lt k + [(x_2,y_2) = (0,0)]$。

综上,定义一个 $S$ 中的元素 $(x,y)$ 的类别属性为 $(x \cdot d_y - y \cdot d_x,x \bmod {d_x},y \bmod {d_y})$,只有两个 $S$ 中的元素的类别属性相同,它们才可能产生相同的位置。

考虑开一个 map,存储每一种类别属性的所有元素。

回到原来问题,此时可以发现,和 $(x_1,y_1)$ 处于同一种类别属性的元素中,有价值的仅包括 $x$ 方向,$y$ 方向上分别最接近的两个元素(总共 $4$ 个元素)。

当前元素产生的新位置的形式为 $(x_1 + k\cdot dx,y_1 + k\cdot dy)$,而原本情况下 $0 \le k \lt K + [(x_1,y_1)=(0,0)]$。

这些邻居元素,实际的影响就是使得一段前缀或者后缀的 $k$ 不合法,分类讨论一下就可以了。

map 里面开两个 set,分奇偶分别存储所有的位置就可以了。

对于 $(0,0)$ 这个特殊的点,它有一个 $k$ 可以等于 $K$ 的特权,为了简单处理这种情况,可以额外建立一个节点 $(dx,dy)$。

T5

先预处理 $f_u$ 表示 $u$ 到所有子树内节点的距离,以及 $siz_u$ 表示 $u$ 子树大小。

然后,再次 dfs 整颗树,并且搜索到每个节点时,都记录其子树外的节点到其距离总和,从父节点到子节点递归的时候,实时更新就可以。

题解:AT_abc219_f [ABC219F] Cleaning Robot

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-08-06 22:13:02

模拟赛考了这题,场上因为细节写挂,差点通过这题,特此记录。

首先,观察一下最终走过的点的坐标形式都是什么。

令前 $n-1$ 次移动到达的点的位置集合(包括一开始的 $(0,0)$)为 $S$,第 $n$ 次移动后的点的位置为 $(dx,dy)$。

那么,对于所有 $Kn$ 次移动中到达过的点 $(x,y)$,必然满足以下两种情况至少一个:

  • 存在一个非负整数 $k \lt K$,以及 $(sx,sy) \in S$,使得 $(sx+k\cdot dx,sy+k\cdot dy) = (x,y)$
  • 存在一个非负整数 $k \le K$,使得 $(x,y) = (k\cdot dx,k\cdot dy)$

换个理解方式,就是对于任意 $(x,y) \in S,0 \le k \lt K$,$(x+k\cdot dx,y + k \cdot dy)$ 必然存在于最终经过的路径里,对于任意 $k \le K$,$(k\cdot dx,k \cdot dy)$ 也必然存在于最终经过的路径里。

首先,由于我不喜欢比较重要的地方出现负数,所以,如果 $dx,dy$ 某一个小于 $0$,那么就将整个的对应的横向或纵向翻转(比如说,U 换成 DL 换成 R)。

答案一定不会改变,因为是对称的,然后,就能保证 $dx,dy \ge 0$。

然后,考虑按照顺序遍历 $S$ 中的每个元素,依次考虑遍历到的新元素,事实上对答案新增了多少个位置的贡献。

此时,就需要分析怎么计算当前的元素产生的位置里面,有哪些是和之前遍历过的元素产生的位置重复了。

回想一下判定条件。

假设当前扫描到了 $S$ 中的元素 $(x_1,y_1)$,然后需要知道其与 $S$ 中另一个元素 $(x_2,y_2)$ 有哪些位置重合。

  • 首先,需要满足 $\frac{x_1-x_2}{dx} = \frac{y_1-y_2}{dy}$。
    本条件的原因是,加上的 $dx,dy$ 分别乘上的倍数都是相等的。
    本条件可以转化为 $x_1\cdot dy - y_1 \cdot dx = x_2 \cdot dy - dx \cdot y_2$,这样等式两边分别都只包含来自同一个元素的值,以及差不多相当于常量的 $dx,dy$。
  • 其次,需要满足 $x_1 \equiv x_2 \pmod {dx},y_1 \equiv y_2 \pmod {dy}$。
    如果不满足这个,那么当然是不能相等的,因为不能有小数。
  • 然后,需要满足 $|\frac{x_1-x_2}{d_x}| \lt k + [(x_2,y_2) = (0,0)]$。

综上,定义一个 $S$ 中的元素 $(x,y)$ 的类别属性为 $(x \cdot d_y - y \cdot d_x,x \bmod {d_x},y \bmod {d_y})$,只有两个 $S$ 中的元素的类别属性相同,它们才可能产生相同的位置。

考虑开一个 map,存储每一种类别属性的所有元素。

回到原来问题,此时可以发现,和 $(x_1,y_1)$ 处于同一种类别属性的元素中,有价值的仅包括 $x$ 方向,$y$ 方向上分别最接近的两个元素(总共 $4$ 个元素)。

当前元素产生的新位置的形式为 $(x_1 + k\cdot dx,y_1 + k\cdot dy)$,而原本情况下 $0 \le k \lt K + [(x_1,y_1)=(0,0)]$。

这些邻居元素,实际的影响就是使得一段前缀或者后缀的 $k$ 不合法,分类讨论一下就可以了。

map 里面开两个 set,分奇偶分别存储所有的位置就可以了。

对于 $(0,0)$ 这个特殊的点,它有一个 $k$ 可以等于 $K$ 的特权,为了简单处理这种情况,可以额外建立一个节点 $(dx,dy)$。

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
#define int ll
#define pii pll
#define vi vector<ll>
const int maxn=2e5+5;
int n;
char s[maxn];
ll k;
struct REM{
    ll mx=-2e18,my=-2e18,cha=-2e18;
    void init(){
        mx=-2e18,my=-2e18,cha=-2e18;
    }
    bool operator<(const REM& b)const{
        if(mx!=b.mx)return mx<b.mx;
        if(my!=b.my)return my<b.my;
        return cha<b.cha;
    }
};
map<REM,set<int> > oklistx,oklisty;
vector<pii> rem;
ll dx,dy;
void solve(int id_of_test){
    scanf("%s",s+1);
    n=strlen(s+1);
    read(k);
    {
        pii cur=mkpr(0,0);
        FOR(i,1,n){
            rem.eb(cur);
         \/\/   printf("%d %d\n",cur.fi,cur.se);
            if(s[i]=='L')cur.fi--;
            if(s[i]=='R')cur.fi++;
            if(s[i]=='U')cur.se++;
            if(s[i]=='D')cur.se--;
        }
        dx=cur.fi,dy=cur.se;
    }
    {
        if(dx<0){
            FOR(i,1,n){
                if(s[i]=='L')s[i]='R';
                else if(s[i]=='R')s[i]='L';
            }
        }
        if(dy<0){
            FOR(i,1,n){
                if(s[i]=='U')s[i]='D';
                else if(s[i]=='D')s[i]='U';
            }
        }
        rem.clear();
        pii cur=mkpr(0,0);
        FOR(i,1,n){
            rem.eb(cur);
         \/\/   printf("%d %d\n",cur.fi,cur.se);
            if(s[i]=='L')cur.fi--;
            if(s[i]=='R')cur.fi++;
            if(s[i]=='U')cur.se++;
            if(s[i]=='D')cur.se--;
        }
        dx=cur.fi,dy=cur.se;
    }
      
  \/\/  printf("dx %d dy %d\n",dx,dy);
    if(!dx&&!dy){
        auto tmp=rem;
        sort(tmp.begin(),tmp.end());
        printf("%lld\n",ll(unique(tmp.begin(),tmp.end())-tmp.begin()));
        return;
    }
    {
        int cnt=0;
        ll ans=0;
        for(auto [x,y]:rem){
            cnt++;
            REM tmp;
            tmp.init();
            if(dx)tmp.mx=(x%dx+dx)%dx;
            else tmp.mx=x;
            if(dy)tmp.my=(y%dy+dy)%dy;
            else tmp.my=y;
            if(dx&&dy){
                tmp.cha=x*dy-y*dx;
            }
            ll l=0,r=k-1;\/\/ 至少增长 0 次,最多增长 k-1 次。
            if(cnt==1){
                r++;
            }
            {
                {\/\/ 处理 x 方向限制
                    auto& okl=oklistx[tmp];
                    auto pos=okl.lower_bound(x);
                    if(pos!=okl.end()){
                        \/\/ 第一个大于等于 x 的位置。
                        if(dx>0){
                            \/\/ 当前主动向 x 更大延申
                            ckmn(r,(*pos-x)\/dx-1);
                        }
                    }
                    if(pos!=okl.begin()){
                        pos--;
                        \/\/ x 更小的位置
                        if(dx>0){
                            \/\/ 需要考虑更小位置向当前延申
                            ckmx(l,((*pos+(k-1)*dx)-x)\/dx+1);

                        }
                    }
                }
                #define x y
                #define dx dy
                #define oklistx oklisty 
                {\/\/ 处理 x 方向限制
                    auto& okl=oklistx[tmp];
                    auto pos=okl.lower_bound(x);
                    if(pos!=okl.end()){
                        \/\/ 第一个大于等于 x 的位置。
                        if(dx>0){
                            \/\/ 当前主动向 x 更大延申
                            ckmn(r,(*pos-x)\/dx-1);
                        }
                    }
                    if(pos!=okl.begin()){
                        pos--;
                        \/\/ x 更小的位置
                        if(dx>0){
                            \/\/ 需要考虑更小位置向当前延申
                            ckmx(l,((*pos+(k-1)*dx)-x)\/dx+1);

                        }
                    }
                }
                #undef x
                #undef dx
                #undef oklistx
            \/\/    printf("[%lld %lld]\n",l,r);
                ans+=max(0ll,r-l+1);
            }
            {
                REM tmp;
                tmp.init();
                if(dx)tmp.mx=(x%dx+dx)%dx;
                else tmp.mx=x;
                if(dy)tmp.my=(y%dy+dy)%dy;
                else tmp.my=y;
                if(dx&&dy)tmp.cha=x*dy-y*dx;
                oklistx[tmp].insert(x);
                oklisty[tmp].insert(y);
                if(cnt==1){
                    oklistx[tmp].insert(x+dx);
                    oklisty[tmp].insert(y+dy);
                }
            }
        }
        printf("%lld\n",ans);
    }
}
signed main()
{
	int T;
	T=1;
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}
\/*
1. 对题意的理解能否和样例对的上?  
2. 每一步操作,能否和自己的想法对应上?  
3. 每一步操作的正确性是否有保证?  
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?  
*\/

ABC418F 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-08-11 09:44:06

题意

给定一个长度为 $n$ 的整数数组 $a$,下标从 $1$ 编号到 $n$。

有一个 $1 \times n$ 的网格图,每个格子要么放茶,要么放咖啡,有以下两个条件:

  1. 任意两个相邻格子,至少要有一个格子放茶。
  2. 对于任意 $i$,使得 $a_i \neq -1$,那么前 $i$ 个格子强制恰好有 $a_i$ 个格子放咖啡。

求有多少种合法的摆放方案。

分析性质

任意两个相邻格子,至少要有一个格子放茶。

这个条件等价于:任意两个放咖啡的格子不能相邻。

对于第二个条件,本质上,将一段前缀的格子划分成了若干个区间,每个区间的咖啡数量是确定的,剩下一段后缀的咖啡数量则不确定。

暴力做法

考虑左到右有 $m$ 个格子,且咖啡数量确定为 $k$,那么方案数是多少。

这个问题可以转化为在 $m$ 个连续位置里面选择 $k$ 个两两不相邻的位置的方案数。

再次转化,发现这个问题等价于有 $m-k$ 个物品,需要选择 $k$ 个这些物品的间隙,分别插入一个新的物品,那么方案数就是 $\binom{m-k+1}{k}$。

为了简化表示,令上述问题的答案为 $g(m,k)$。

首先,仅考虑已经确定咖啡总数的那些区间。

假设第 $i$ 个区间的长度为 $l_i$,咖啡数量为 $x_i$。

那么设计出状态 $f_{i,0/1}$ 表示前 $i$ 个区间已经确定,且最后一个区间的最后一个位置是否为咖啡,那么总合法方案数是多少。

考虑状态转移,以下默认每种转移的结果都累加。

  1. 上一个区间的末尾没有咖啡,这次的区间末尾也没有咖啡。
    $f_{i,0} \leftarrow f_{i-1,0} \cdot g(l_i-1,x_i)$。
  2. 上一个区间的末尾有咖啡,这次的区间末尾没有咖啡。
    此时分类讨论两种情况。
    • 若 $l_i \ge 2$。
      $f_{i,0} \leftarrow f_{i-1,1} \cdot g(l_i-2,x_i)$。
    • 若 $l_i = 1$。
      $f_{i,0} \leftarrow [x=0]$。
  3. 上一个区间的末尾没有咖啡,这次的区间末尾有咖啡。
    • 若 $l_i \ge 2$。
      $f_{i,1} \leftarrow f_{i-1,0}\cdot g(l_i-2,x_i-1)$。
    • 若 $l_i = 1$。
      $f_{i,1} \leftarrow [x=1]$。
  4. 上一个区间的末尾有咖啡,这次区间的末尾也有咖啡。
    • 若 $l_i \ge 3$。
      $f_{i,1} \leftarrow f_{i-1,1} \cdot g(l_i-3,x_i-1)$。
    • 若 $l_i = 2$。
      $f_{i,1} \leftarrow [x=1]$。
    • 若 $l_i = 1$。
      无解。

上述 DP 解决了由已经确定总咖啡数量的区间组成的那一段前缀。

但是还剩下一段后缀,没有咖啡数量限制,仅被咖啡不能相邻的条件约束。

如果设计出一种新的 DP 解决这一部分,会比较麻烦,考虑复用之前的 DP。

一种比较简单的方法,可以将剩下的那一段后缀划分为若干个单个元素组成的区间,沿用原来的 DP 状态。

对于状态转移,则枚举当前区间有一个咖啡还是有零个咖啡,分别按照之前的转移方式算出结果,加起来就是总方案数。

现在,成功构造出了一种单次 $O(n)$ 的做法。

优化

每次修改,都是修改某位置的前缀和。

注意到,它实际上最多影响两个区间的总和。

另外注意到,原来的 DP 状态,完全可以写成矩阵的形式,通过矩阵乘法转移,这样就支持了线段树快速合并答案。

现在最大的问题,在于每次操作都可能增加一个区间,或删除一个区间,这样就没法直接维护每个区间的答案。

但这个也是可以简单解决的。

首先开一个 set 维护所有存在的区间。

对于每个区间,可以只在其中一个位置维护其整个区间的转移矩阵,区间其他位置全部填充为单位矩阵。

每次修改,都将可能影响的两个区间全部填充为单位矩阵,然后重新填就行了。

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
const ll mod=998244353;
const int maxn=2e5+5;
int n,q;
struct Matrix{
	ll mat[2][2];
	void init(){
		memset(mat,0,sizeof mat);
	}
	void unit(){
		init();
		mat[0][0]=mat[1][1]=1;
	}
	Matrix operator*(const Matrix& b)const{
		Matrix res;
		res.init();
		FOR(i,0,1){
			FOR(k,0,1){
				FOR(j,0,1){
					(res.mat[i][j]+=mat[i][k]*b.mat[k][j])%=mod;
				}
			}
		}
		return res;
	}
	Matrix operator+(const Matrix& b)const{
		Matrix res;
		FOR(i,0,1){
			FOR(j,0,1){
				(res.mat[i][j]=mat[i][j]+b.mat[i][j])%=mod;
			}
		}
		return res;
	}
};
struct SGT{
	int lef[maxn<<2],rig[maxn<<2];
	Matrix res[maxn<<2],cov[maxn<<2];
	bool tag[maxn<<2];\/\/ 本节点存在 lazytag
	void pushup(int pos){
		res[pos]=res[pos<<1]*res[pos<<1|1];
	}
	void upd(int pos,Matrix _cov,bool _tag){
		tag[pos]=_tag;
		cov[pos]=_cov;
		res[pos]=_cov;
		\/\/ 理论上应该是 qpow(_cov,r-l+1)
		\/\/ 但是,赋值要么是单位矩阵,要么是区间长度为 1
	}
	void pushdown(int pos){
		if(!tag[pos])return;
		upd(pos<<1,cov[pos],tag[pos]);
		upd(pos<<1|1,cov[pos],tag[pos]);
		tag[pos]=0;
	}
	void bd(int pos,int l,int r){
		lef[pos]=l,rig[pos]=r,res[pos].unit(),tag[pos]=0;
		if(l==r)return;
		int mid=(l+r>>1);
		bd(pos<<1,l,mid);
		bd(pos<<1|1,mid+1,r);
	}
	void chg(int pos,int l,int r,Matrix _cov){
		if(lef[pos]>=l&&rig[pos]<=r){
			upd(pos,_cov,1);
			return;
		}
	\/\/	printf("pos %d\n",pos);
		pushdown(pos);
		int mid=(lef[pos]+rig[pos]>>1);
		if(mid>=l)chg(pos<<1,l,r,_cov);
		if(mid<r)chg(pos<<1|1,l,r,_cov);
		pushup(pos);
	}
}sgt;
ll qpow(ll a,ll b){
	ll ret=1;
	for(;b;a=a*a%mod,b>>=1){
		if(b&1)ret=ret*a%mod;
	}
	return ret;
}
ll inv(ll a){
	return qpow(a,mod-2);
}
ll fact[maxn*2],factinv[maxn*2];
ll C(int n,int m){
\/\/	printf("C(%d,%d) = %lld\n",n,m,((n>=m)?fact[n]*factinv[m]%mod*factinv[n-m]%mod:0));
	return ((n>=m)?fact[n]*factinv[m]%mod*factinv[n-m]%mod:0);
}
ll F(int len,int x){
	if(len<0||x<0)return 0;
	return C(len-x+1,x);
}
Matrix calc(int len,int x){
	Matrix res;
	res.init();
	\/\/ 原来末尾没有,现在末尾没有
	{
		res.mat[0][0]=F(len-1,x);
	}
	\/\/ 原来末尾有,现在末尾没有
	{
		if(len>=2){
			res.mat[1][0]=F(len-2,x);
		}else{
			\/\/ 第一个排除位置,与最后一个排除位置重合,实际只关心唯一一个位置是否不填
			res.mat[1][0]=(x==0);
		}
	}
	\/\/ 原来末尾没有,现在末尾有
	{
		if(len>=2){
			res.mat[0][1]=F(len-2,x-1);
		}else{
			res.mat[0][1]=(x==1);	
		}
	}
	\/\/ 原来末尾有,现在末尾有
	{
		if(len>=3){
			res.mat[1][1]=F(len-3,x-1);
		}else{
			res.mat[1][1]=(len==2&&x==1);
		}
		
	}
	return res;
}
set<int> poslist; 
int pre[maxn];
Matrix ways_nolim[maxn];\/\/ 没有咖啡数量的限制,仅有咖啡不能相邻的限制
Matrix unitmat;
void print(){
	Matrix ans;
	ans.init();
	ans.mat[0][0]=1;
	ans=ans*sgt.res[1];
	auto ptr=prev(poslist.end());
	ans=ans*ways_nolim[n-*ptr];
	printf("%lld\n",((ans.mat[0][0]+ans.mat[0][1])%mod+mod)%mod);
}
void solve(int id_of_test){
	read(n);
	read(q);
	vector<pii> queries;
	FOR(i,1,q){
		int x,y;
		read(x);
		read(y);
		queries.eb(mkpr(x,y));
	}
	sgt.bd(1,1,n);
	poslist.insert(0);
\/\/	print();
\/\/	printf("ways_no_lim %lld %lld\n",ways_nolim[n].mat[0][0],ways_nolim[n].mat[0][1]);
\/\/	return;
	for(auto [pos,_val]:queries){
		if(pre[pos]==_val){
			print();			
			continue;
		}
		if(pre[pos]==-1){
			\/\/ 增加新的右端点
			pre[pos]=_val;
			auto _rg=poslist.lower_bound(pos);
			auto _lf=_rg;
			_lf--;
			int lf=*_lf+1,rg;
			sgt.chg(1,lf,pos,unitmat);\/\/ 删除 [lf,pos]
			if(_rg!=poslist.end()){
				rg=*_rg;
				{
					\/\/ [lf,rg] 是原来 pos 所在的完整区间
					\/\/ 首先删除原来区间		 
					sgt.chg(1,lf,rg,unitmat);
				}
				if(pos!=rg){
					\/\/ 加入 [pos+1,rg]
					sgt.chg(1,pos+1,pos+1,calc(rg-(pos+1)+1,pre[rg]-pre[pos]));
				}
			}
			\/\/ 加入 [lf,pos]
			sgt.chg(1,lf,lf,calc(pos-lf+1,pre[pos]-pre[lf-1]));
			poslist.insert(pos);
		}else if(_val==-1){
			\/\/ 减少一个右端点
			pre[pos]=-1;
			auto _rg=poslist.find(pos);
			auto _lf=_rg;
			_lf--;
			_rg++;
			int lf=*_lf+1;
			int rg;
			sgt.chg(1,lf,pos,unitmat);
			if(_rg!=poslist.end()){\/\/ 存在下一个右端点,提供合并
				rg=*_rg;
				\/\/ 删除原来区间
				sgt.chg(1,lf,rg,unitmat);
				\/\/ 加入新的
				if(pre[rg]!=-1)sgt.chg(1,lf,lf,calc(rg-lf+1,pre[rg]-pre[lf-1]));
			}else{
				sgt.chg(1,lf,n,unitmat);\/\/ 否则,后面整段都没了。
			}
			poslist.erase(pos);
		}else{
			\/\/ 简单地修改值
			pre[pos]=_val;
			auto _rg=poslist.find(pos);
			auto _lf=_rg;
			_lf--;
			_rg++;
			int lf=*_lf+1;
			int rg;
			sgt.chg(1,lf,pos,unitmat);
			if(_rg!=poslist.end()){
				rg=*_rg;
				sgt.chg(1,lf,rg,unitmat);
				if(pre[rg]!=-1)sgt.chg(1,rg,rg,calc(rg-(pos+1)+1,pre[rg]-pre[pos]));
			}
			sgt.chg(1,lf,lf,calc(pos-lf+1,pre[pos]-pre[lf-1]));
		}
		print();
	}
}
int main()
{
	unitmat.unit();
	fact[0]=1;
	FOR(i,1,maxn*2-1)fact[i]=fact[i-1]*i%mod;
	factinv[maxn*2-1]=inv(fact[maxn*2-1]);
	REP(i,maxn*2-1,1)factinv[i-1]=factinv[i]*i%mod;
	ways_nolim[0].unit();
	auto sgway=calc(1,0)+calc(1,1);
\/\/	return 0;
	FOR(i,1,maxn-1){
		ways_nolim[i]=ways_nolim[i-1]*sgway;
	}
	FOR(i,1,maxn-1){
		pre[i]=-1;
	}
	int T;
	T=1;
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}
\/*
1. 对题意的理解能否和样例对的上?
2. 每一步操作,能否和自己的想法对应上?
3. 每一步操作的正确性是否有保证?
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?
*\/

【类欧几里得算法】at_practice2_c

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-02-16 15:28:04

省选前复习一下学过的算法。

考虑一个式子 $f(a,b,c,n) = \sum\limits_{i=0}^n \lfloor \frac{ai+b}{c} \rfloor$。

考虑拆分一下,能否把 $a,b$ 取模 $c$:
$f(a,b,c,n) = \sum\limits_{i=0}^n \lfloor \frac{\lfloor \frac{a}{c}\rfloor ci+i(a \bmod c)+\lfloor \frac{b}{c}\rfloor c +b \bmod c}{c}\rfloor$。
$=\sum\limits_{i=0}^n \lfloor \frac{(a\bmod c)i+\lfloor \frac{b}{c}\rfloor}{c}\rfloor + \lfloor \frac{a}{c}\rfloor(\sum\limits_{i=1}^n i)+(n+1)(b \bmod c)$
$= f(a \bmod c,b \bmod c,c,n) + \lfloor \frac{a}{c} \rfloor \frac{(1+n)n}{2}+(n+1)(b \bmod c)$.

如果 $a \ge c \lor b \ge c$,那么直接递归。

否则,需要找到其他办法减小这个问题的规模。

简单变形一下。

$f(a,b,c,n) = \sum\limits_{i=0}^n \sum\limits_{j = 0}^{\lfloor \frac{ai+b}{c}\rfloor-1}1 $
$= \sum\limits_{j=0}^{\lfloor {\frac{ai+b}{c}}\rfloor-1}\sum\limits_{i=0}^n [\lfloor\frac{ai+b}{c}\rfloor \ge j]$.

考虑一下 $i,j$ 的大小关系。

$j \le \lfloor\frac{ai+b}{c}\rfloor-1$
$j \le \frac{ai+b}{c}-1$
$ai+b \ge jc+c$
$ai \ge jc+c-b$
$a \gt \frac{jc+c-b-1}{i}$

$f(a,b,c,n) = \sum\limits_{j=0}^{\lfloor \frac{an+b}{c}\rfloor-1}(n-\lfloor\frac{cj+c-b-1}{a}\rfloor)$
令 $T = \lfloor \frac{an+b}{c}\rfloor-1$。
原式转化为 $(T+1)n-f(c,c-b-1,a,T)$。

容易发现这个过程和欧几里得算法的复杂度一样。

To do list

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-02-18 11:02:58

简单记录一下待完成 task list.

  1. 轮廓线 dp(插头 dp)
  2. 欧拉回路,路径等。 finished
  3. LCT finished
  4. CDQ 分治 finished
  5. brovuka finished
  6. 点双联通分量 finished
  7. min25
  8. 斜率优化 finished
  9. 杜教筛 finished
  10. 卡特兰数
  11. 斯特林数
  12. 决策单调性优化 dp finished
  13. O(n) SA height
  14. SAM
  15. PAM
  16. 高斯消元。

SDOI2025 游记

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-02-18 12:25:13

预告

我最终失败了,本文记录考前的状态,以及考试状态。

废话

NOIP 过了 T1 之后一直在调 T2,最后比一等线低 4 分,无敌了。还见到了 atc rating 比我低 1000 的考的比我好的。

赛前没调整好时间,导致赛时状态很差可能是一个原因,就认了。

希望能翻进 D 线,虽然我不知道今年 D 是不是得看 NOIP,希望不是。

12 月

恶补数学,学会了一些基础算法,比如莫比乌斯反演。

1 月

学了点省选的(应该算基础?)算法,比如说杜教筛,min25 筛,LCT 等,刷了点基础题。

vp 了一些远古 Edu,最好的一次到了当年 1+2 的 rk12,最差也有 4 题。

然而这些题都是些陈年典题,套路早就被刷烂了,我也知道这些排名是假的没用。

最值得纪念的或许是第一次自己做出了一道黑题(CF 3200),不过这题跟板子没啥区别,感觉。

2 月

北京参加梦熊集训。

前面阶段在 pku 进行,每场都打的很艰难,排名都不靠前,还考了几个倒数,感觉不少题只要给个大致方向就几乎解决,但是就是想不到。

后面在 rdfz 举行,舍友是 @KSCD_ @Iceturky @_fairytale_

每天晚上收手机,不过无所谓,晚上太晚,第二天早上就得在模拟赛补。

但晚上总是一堆人带着电脑回去卷题。

@KSCD_ 等人晚上仍然打 CF,而且还打前几名。

印象较深的一次是参与了 6 个人的真心话大冒险,具体的不好说,净是些黑历史曝光。

@_fairytale_ 的知乎收藏夹看起来很有意思。

一直由于社恐没参与下午的羽毛球,直到最后一次,再不去就没机会了。

另外得到了一个 @KSCD_ 的徽章。

2025.2.26

集训结束,回去。

值得一提的是,路上一直在 telegram 用 Cnglish 和一个自称是伊朗人的聊天,互相发了很多自己所在城市及学校的照片,他还说想来中国当老师???但他和我同龄啊。

跟着 @Iceturky 去吃饭,路上拍了省选前最后一个照片,潍坊的夜景。

2025.2.27

复习了下板子,摆烂。

2025.2.28

这次再次和 @zibenlun 同一个酒店房间。

和他玩了会国际象棋,然后我们各自摆各自的了。

总之,一种很复杂的心情。

试机的时候得到了一个 @cancan1234 的徽章。

写了两题,看起来机器挺快的。

2025.3.1

梦见省选查分,吓醒了。

早上怕考场困,没敢多吃,虽然特别好吃。

8:30

开 T1 就被暴击,完全没有思路。

过了一会,我想了一个看起来能 50 的做法,就先扔了。

9:30

经过长时间思考,T1,T2 反复横跳,并没有多想出一点分。

10:00

T3 没有思路。
立刻开始写 T2,T3 的基础暴力。

10:30

现在总分 28 分,时间过去大半,心态崩了。

决定:all in T1。

10:40

突然发现这不就是个唐题吗?随便贪心一下就可以了。

但是好像很难写啊,仅仅是二分的 $O(1)$ check 函数就写了十几行。

但是我知道如果做不出来就完蛋了(其实从最后结果来看,都一样),决定硬拼。

11:30

写完了,但是过不去样例。

盯着代码看了好几遍,找出了一个符号打错。

11:40

小样例过了,大样例全错。

我开始暴力模拟大样例的前几个测试点,把它们用在纸上画出来,发现我的做法能得出正确结果,应该只是写挂了。

11:50

大样例前十几个 case 过了,到了大约第 20 个又 wa 了,压力特别大。

12:00

手动模拟大样例找出了错,通过了所有大样例。

之后,就是一定的卡常,将其从极限数据的 1.5s+ 压到了 0.1s。

T2,T3 仍然没有思路。

13:00

问了一圈,大部分是 128,但 NOIP 差的 100 多分还没还上。

然后摆了一整天。

Day2

开考前感受到了特别大的压力。

8:30

感觉像是可以建模为图论题,但是没想出来怎么建模。

总之,不知道咋回事就是一种想摆的感觉,我直接扔了。

T2,T3 想了想,看着不像我能做的样子,就写了个暴力。

然后就回去做 T1。

想了几种贪心策略,拼在一起,然而过不去大样例。

随后想特殊性质。

感觉 B 好像可做,就写了一发,过了对应大样例。

随后就是 all in T1,但是没有结果。

出考场

倒闭了,破防了。

出成绩

Day1 没挂分,Day2 为什么挂的不剩几分????

@崔化博 进了省队,Congrats!

@Syrus,@Iceturky,@KSCD_ 等人都没进队,但是 @Iceturky @KSCD_ 可以去 D 类。

@cxm1024 队长!

我不清楚 _fairytale_kingpower 的情况,也不敢去问,希望他们能进。

@PTqwq 应该是能去 NOI,非常的羡慕。

其他人,zgc,gyf,ly,hjr 等人不太清楚,我不敢去问。

提前备考 NOIP2025 了,希望明年能进,大概是意识到了差距了。

NOIP2024 T2 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-02-20 14:28:40

Div.2 C 级别唐氏题,考场上为什么调了 3h+ 还没调出来呢?连 newbie 都能场切这题,我真的是 CM 吗?

首先排序去重,如果遇到条件自己冲突的那方案数就是 $0$。

按照已经确定的点将原序列 $n$ 个位置划分为若干段,除了首段全部由不确定位置组成以外,每一段开头都是一个确定的位置。

  • 首段不确定位置
    这一段的 $a,b$ 随便填,只需要让 $x_i \neq a_i$,那么就显然是合法的。
  • 除了首段,最后一段以外的段
    这类段的特点是第一个位置是确定的,段最后的下一个位置也是确定的。
    段的 $a,b$ 方案书可以通过总方案数减去不合法方案数得到,令长度为 $l$。
    总方案数是 $v^{2l}$。
    不合法方案如何构造呢?显然只能是本段通过一些 $a,b$ 的构造,强迫下一个确定位置的 $x$ 值不等于其给定值。
    第一个位置有 $v$ 的方案数确定第二个位置的值,第二个位置同样有 $v$ 的方案数确定第三个位置的值。
    以此类推,总共有 $v^{len-1}$ 的方案数确定本段最后一个位置的值。 本段最后一个位置总共有 $v-1$ 的方案数($a$ 值等于自己,$b$ 值不等于下一个位置)。 所以,总的不合法方案数是 $v^{len-1}(v-1)$。 总合法方案数是 $v^{2len}-v^{len-1}(v-1)$。
  • 最后一段
    显然 $a,b$ 随便填。
#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
const ll mod=1e9+7;
int n,m,v;
pii lim[100005];
ll qpow(ll a,ll b){
\/\/	printf("qpow %lld %lld\n",a,b);
	ll ret=1;
	for(;b;a=a*a%mod,b>>=1){
		if(b&1)ret=ret*a%mod;
	}
\/\/	printf("ret %lld\n",ret);
	return ret;
}
void solve(int id_of_test){
	read(n);
	read(m);
	read(v);
	FOR(i,1,m){
		read(lim[i].fi);
		read(lim[i].se);
	}
	sort(lim+1,lim+m+1);
	m=unique(lim+1,lim+m+1)-lim-1;
	FOR(i,1,m-1){
		if(lim[i].fi==lim[i+1].fi){
			if(lim[i].se!=lim[i+1].se){
				puts("0");
				return;
			}
		}
	}
	ll ans=1;
	ans=ans*qpow(v,2*(lim[1].fi-1));
	ans=ans*qpow(v,2*(n-lim[m].fi))%mod;
	FOR(i,1,m-1){
		ll res=qpow(v,2*(lim[i+1].fi-lim[i].fi));
	\/\/	printf("pre %lld [%d,%d]\n",res,lim[i].fi,lim[i+1].fi);
		res-=qpow(v,lim[i+1].fi-lim[i].fi-1)*(v-1)%mod;
	\/\/	printf("fff %lld\n",res);
		ans=ans*res%mod;
	}
	printf("%lld\n",(ans+mod)%mod);
}
int main()
{
	int T;
	read(T);
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}


How to AK ABC234

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-03-09 22:26:05

A

给定一串表达式要求计算。

注意到没有未知量,把题目给的表达式复制下来直接输出就可以了。

B

给定 $n$ 个点,求两两之间的距离最大值。

$n$ 很小,暴力枚举两个点就可以了。

C

输出 $10$ 进制下第 $k$ 小的,仅包含 $0,2$ 的数。

注意到 $k \le 10^{18}$,不能使用暴力方法。

观察样例发现在 $k$ 达到上限的时候,答案也才 $60$ 位,所以答案位数最多 $60$ 位。

常用套路,类似递归的方式,逐渐减小问题规模。

假定当前求解的是答案是 $f(k)$。

假定个位为第一位,并且最高非零位是 $x$ 位,那么,至少有 $2^{x-1}$ 个数严格小于答案。

通过这个方法,可以枚举得出答案的最高非零位。

随后,再递归计算 $f(k-2^{x-1})$。

时间复杂度 $O(\log k)$ 级别,如果每次递归都重新枚举最高位,那还会多一个 log。

D

给定一个长度为 $n$ 的数列 $a$,一个正整数 $k$。

要求你对于 $a$ 的每个前缀,求出其第 $k$ 大的值。

维护一个优先队列,每次扩展前缀时候先把新元素加入,然后只要队列有超过 $k$ 个元素,就把最小的删掉。

E

称一个数字是算术数,当且仅当任意相邻前后两位的差值是一样的。

给定 $x \le 10^{17}$,求最小的大于等于 $x$ 的算术数。

注意到满足条件的数的位数一定与 $x$ 相同,因为可以使用 $99999999$ 这样形式的。

注意到 $x$ 的位数很少,可以暴力枚举最高位以及相邻两项的差值。

从小到大枚举最高位,从小到大枚举相邻低位减高位的值,第一个合法的数就是答案。

F

给定字符串 $S$,你可以随意提取 $S$ 的任意子序列(可以不连续),然后重新排列,问能得到的字符串数量。

$|S| \le 5000$。

注意到,其实有效的信息仅仅是 $S$ 中每种字符出现了多少次。

az 的字符编号 $1$ 到 $26$,令 $cnt_i$ 代表字符 $i$ 的出现次数。

注意到,难以处理的地方在于相同字符的排列组合,如果一个一个字符的加入,则需要在状态里面记录每种字符之前已经有多少个,这是不可能的。

所以,现在直接考虑每种字符有多少个加入最终字符串,整体考虑。

$dp_{i,j}$ 代表前 $i$ 种字符总共选了 $j$ 个的方案数。

每种字符通过枚举自己选择了多少个进行转移。

$dp_{i,j} = \sum\limits_{k=0}^jdp_{i-1,j-k}\binom{k+j-1}{j}$。

时间复杂度 $O(n^2)$,稍微分析一下就知道不需要乘 $26$。

G

给定一个长度为 $n$ 的数列,注意到有 $2^{n-1}$ 种方式将其划分为不同的连续子序列。

对每种划分方式下,子序列的极差的乘积进行求和。

$n \le 3\cdot 10^5$。

令 $f_i$ 代表前 $i$ 个元素构成的序列的答案。

$f_0=1$.
$f_i = \sum\limits_{j=1}^i f_{j-1}(\max\limits_{k=j}^ia_k-\min\limits_{k=j}^ia_k)$。

考虑 max,min 分开计算,这两个事实上差不多,这里只讨论 min 的情况。

考虑每个 $a_j$ 对于答案的贡献。

事实上,如果存在 $x \lt y$,满足 $a_x \ge a_y$,那么 $a_x$ 就没有存在的必要。

使用单调队列优化,使得有效的 $a$ 值都是从前到后降序的,同时每个 $a$ 值的贡献区间就是自己前面这一段一直到上一个 $a$ 值。

想到这里其实已经做完了,单调队列维护的时候顺便维护一下答案就做出来了。

时间复杂度 $O(n)$。

Ex

给定二维平面上的 $n$ 个点,一个整数 $k$,要求给出所有的距离小于等于 $k$ 的点对,保证符合要求的点对数量不超过 $4 \cdot 10^5$。

参考的题解做法,就当一个 trick。

做法 1 - 洛谷第二篇题解的做法

解法很简单,将这个平面划分为一个个大小为 $k \times k$ 的正方形,对于每个点,枚举以其正方形为中心的九宫格内的所有点。

这个做法显然能求出所有的点对,但是现在为什么它的时间复杂度是对的呢?

考虑将一个 $k\times k$ 的正方形划分为 $4$ 个小正方形,那么每个小正方形内部的点必然互相到达。

假设这个 $k\times k$ 的正方形内有 $m$ 个点,那么这个正方形自己内部的答案也就是 $O(m^2)$ 级别的。

此时,返回去再考虑两个相邻的 $k\times k$ 正方形的点对枚举,假设一个大小为 $p$ ,另一个为 $q$,那么计算量将是 $O(pq) \le O(\max^2(p,q))$。

考虑均摊分析,将相邻 $k\times k$ 正方形点对枚举的计算量算到点多的正方形上,假设这个正方形大小为 $s$,那么这个正方形的计算量贡献将最多是 $10s^2$,而这个正方形的内部合法点对数量就是 $O(s^2)$ 的,而总体的合法点对数量题目给了限制,所以复杂度正确。

这个做法本质上我认为是先通过一下操作排除掉一些不必要的节点,然后通过一定分析证明排除掉不必要的节点之后,复杂度在一定范围以内。

做法 2 - 第一篇题解的做法

随机将所有点旋转一个角度。

然后,横坐标升序,赌距离在 $k$ 以内的点在列表里面不会差的很远,对于每个点,枚举接下来大约 $2 \cdot 10^4$ 个节点。

本质上将所有点的横纵坐标重新随机分配了。

这样的话,由于题目对于答案的限制,每个点对答案的贡献相对平均,所以只需要枚举少量的点。

Codeforces Round 896 (Div. 1) A-C 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-03-12 20:15:08

感觉都是有些套路的思维题。

A

题意

给定一个 $n\times m$ 的矩阵 $M$。

矩阵的每一行都应该是一个长度为 $m$ 的由 $[0,m-1]$ 的整数构成的排列。

一列的价值是这一列所有数的 MEX,整个矩阵的价值是所有列的价值的 MEX,要求你为矩阵 M 的每个位置赋值,使得在矩阵合法的前提下,矩阵的价值最大。
$nm \le 2\cdot 10^5$。

解法

行列从 $0$ 编号方便一些。

考虑依次构造出 MEX 为 $0,1,2,\cdots,m-1$ 的列。

第一行先按照 MEX 顺序来: $m-1,0,1,2,3,\cdots,m-2$。
随后每一行,就将 $0$ 开头的升序序列向右移一位,左边空余的,则将剩下的数字降序填就行。

如果某一行出现了 $j$ 列的值刚好等于 $j$,那么也不要紧,和 $j-1$ 的值交换一下就没事了,反正同一行最多一个这样的位置。

B1

本题的简单版本,但其实和困难版没差多少。

题意

有 $n$ 个人,每个人初始有 $a_i$ 个糖果。
每个人必须给另一个人赠送 $2^x$ 个糖果,必须从另一个人收到 $2^y$ 个糖果,其中 $x,y$ 是非负整数。

其中,每个人可以自由选择把糖果赠送给谁,每个人收到和赠送的糖果数也可以任意选择,只需要满足上述性质

同时,赠送操作的顺序也是任意的。

问是否存在一种方案,使得最后所有人的糖果数量相同。

$n \le 2\cdot 10^5$,值域 $10^9$。

解法

最后每个人的糖果数量 $avg$ 可以直接算出来。

然后,每个人的 $x,y$ 也可以算出来(如果某个人的初始值等于 $avg$,可以忽略这个人)。

有解当且仅当对于任意 $2^x$,需要得到 $2^x$ 的人数与需要得到 $2^y$ 的人数相同,并且对于 $1 \le \forall i \le n, popcount(|a_i-avg|) \le 2$。

B2

困难版,赛时通过本题可以到达 Master。

题意

与 B1 的区别:在本题,每个人可以向不超过一个人赠送糖果,接受不超过一个人的糖果。

解法

事实上,只有在 $popcount(|a_i-avg|)=1$ 的时候才有区别,此时,在 B1 限制条件下,$a_i-avg$ 的值强制拆为 $2^p-2^q(p\neq q) $ 的形式,但在 B2,它不仅可以保持原来 B1 的形式(赠送 $2^q$,收获 $2^p$ 个),也可以仅仅是 $2^k$ 形式,即仅赠送或仅接受 $2^k$ 个糖果。

此时,考虑还是按照 B1 限制下的形式,如果方案不合法再转化为 B2 限制下的新形式。

从高位开始考虑,如果当前位接受与赠送数量不同,那么就转化形式。

C

感觉比 A 简单,但是 CF 评分 2400,并且场切这题最高可以到达黑红名。

题意

给定一个大小为 $n$ 的完全二叉树,点权范围是 $[1,m]$。

求所有 $m^n$ 个可能的情况,两两点对之间路径权值最大值的和的和。
$n \le 10^{18},m\le 10^5$。

解法

考虑 $1 \le \forall i \le m$,求出路径点权最大值为 $i$ 的方案数。

没法直接计算,可以用差分的方式,计算出路径点权最大值小于等于 $i$ 的方案数。

考虑固定点权最大值限制 $lim$,求解一下函数:

令 $f(n)$ 代表树的大小为 $n$ 的情况下,所有 $m^n$ 种可能下路径点对最大值小于等于 $lim$ 的点对数量总和。

可以拆为左子树,右子树的答案加和,然后计算左右子树之间的答案,以及根作为端点的答案。

令 $g(n)$ 代表树的大小为 $n$ 的情况下,所有 $m^n$ 种可能下,根作为路径端点且点权最大值小于等于 $lim$ 的路径数量总和。

$g(n)=(g(siz(ls))m^{siz(rs)}+g(siz(rs))m^{siz(ls)}+m^{n-1})lim$

$f(n)=f(siz(ls))m^{siz(rs)+1}+f(siz(rs))m^{siz(ls)+1}+g(siz(ls))g(siz(rs))lim+g(n)$。

单次计算时间复杂度 $O(log n)$。

难点在于记忆化。

上述函数需要调用 $m$ 次,考虑提前预处理会到达的 $n$,以及和 $lim$ 无关的项,将 $n$ 映射到 dfn,这样每次记忆化的时候就可以只用开一个 $O(\log n)$ 级别的数组就可以了。

共 320 篇博客