Logo __vector__ 的博客

博客

标签
暂无

题解: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)$ 级别的数组就可以了。

Rayan Programming Contest 2024 - Selection (Codeforces Round 989, Div. 1 + Div. 2) A-F1

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

A

给定 $a,b$,找到最小整数 $m$ 满足 $m \bmod a = m \bmod b$。

考虑答案是 $n$,那么 $n - n \bmod a$ 就是一个小于等于 $n$ 的答案。

所以,答案就是 $lcm(a,b)$。

B

给定长度为 $n$ 的二进制数列,你单次操作可以选择一个长度为 $k$ 的子区间,将这个子区间的所有数变为 $1$,要求最终没有长度大于等于 $m$ 的全 $0$ 子区间,求最少操作次数。

从前往后扫,遇到一个点长度达到 $m$ 了,就从这个点开始操作一次,显然最优的。

C

有一个 $n\times m$ 的矩阵,每个矩阵格子都指向一个相邻的格子。

一部分格子的指向已经确定,问在最优操作方案下,能到达环的格子数量最多是多少个?

先考虑那些已经确定方向的格子。

对于一个这样的格子, dfs 一下,如果最终到达的点是未确定方向的格子,或者回到了起点,那么这个格子就是可以在环里面的,这个结论很容易发现并证明。

对于未确定方向的格子,只要这个未确定格子所在的同类型的格子的连通块大小大于等于 $2$,或者连着一个能达到环的格子,那么这个未确定格子连通块内的所有格子都可以计入答案,这也比较显然。

上述结论都需要画图证明,就先不画图了。

D

题意

给定一个长度为 $n$ 的整数数组,值域 $[0,2]$,仅允许差的绝对值为 $1$ 的数交换位置。

要求构造一组操作方案使得操作次数小于等于 $n$,且最后数组单调不降。

保证至少存在一个 $1$。

不要求最小化操作次数,可以证明一定存在合法方案。

做法

考虑按照升序结果划分为 $3$ 个前后连接的部分,分别代表 $0$ 的位置区间,$1$ 的位置区间,$2$ 的位置区间。

考虑最简单的方式:

首先考虑 $0$ 区间,先把里面的 $1$ 换出去。

然后,考虑 $0$ 区间里面的 $2$,注意到每个 $2$ 需要两次操作才能交换出去。

此部分的最大代价为 $2\min(cnt_0,cnt_1+cnt_2)$。

操作完之后,$0$ 区间已经搞定了,$1$ 区间还有一部分 $2$,$2$ 区间还有一部分 $1$,交换一下就可以了。

此部分的代价为 $\min(cnt_1,cnt_2)$。

vp 赛时随机了 $10^5$ 组数据没出错,提交就过了。

赛后参考了洛谷第一篇题解,证明一下正确性。

总代价 $2\min(cnt_0,cnt_1+cnt_2)+\min(cnt_1,cnt_2)$。

注意到原数组 $0,2$ 交换之后操作次数也是一样。

那么令 $cnt_0 \le cnt_2$。

总代价为 $2cnt_0+\min(cnt_1,cnt_2) \le cnt_0+\max(cnt_1,cnt_2)+\min(cnt_1,cnt_2)=n$

E

题意

给定正整数 $n,k$。
要求构造 $k$ 个不同的长度为 $n$ 的排列,使得每个位置在 $k$ 个排列的总和是相同的。 显然最后每个位置的总和是确定的。

解法

无解比较好判定,这里不记录。

如果 $k$ 是偶数,那么只需要两两配对就可以了。

如果 $k$ 是奇数,那么必然存在一个三元组满足要求。

三元组的构造可以先固定第一个排列为 $1,2,\cdots,n$。

每个位置的值总和是 $\frac{3(n+1)}{2}$,令 $m=\lfloor \frac{n}{2}\rfloor$,那么它可以表示为 $3m+3$

然后,第二三排列的第一个位置临时固定为 $m+1,2m+1$。

随后模拟一下大概就得出来了。

F1

没来得及写代码,以后再补。

ABC396G 题解

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

这题被恶心了很久,不知道为啥一直没有题解。

做法

显然操作顺序不会影响结果,每一行或每一列最多操作一次。

注意到最多 $18$ 列,考虑暴力做法,先枚举哪些列执行了操作。

随后,枚举每一行,如果 $1$ 的数量大于 $0$ 的数量,就翻转这一行。

考虑将每一行视为一个二进制数。

问题转化为下面的形式:

给定一个长度为 $n$ 的非负整数序列 $b$,一个整数 $m$,每个数严格小于 $2^{m}$。
定义 $popcount(x)$ 为 $x$ 的二进制表示中 $1$ 的个数。
求出 $\min\limits_{x=0}^{2^m-1}\sum\limits_{i=1}^n\min(popcount(a_i\oplus x),m-popcount(a_i\oplus x))$。

设 $f_{s,i,j}$ 代表所有数异或 $s$,仅考虑最后 $i$ 位,二进制中 $1$ 的数量是 $j$ 的数有多少个。

遗憾的是,这样仍然很难设计转移方程,主要难点在于,新加入一位 $i'=i+1$ 之后,难以得到新的 $j'$。

考虑一些更严格的限制,$f_{s,i,j}$ 代表所有数异或 $s$,最后 $i$ 位中有 $j$ 位为 $1$,且前 $m-i$ 位都是 $0$ 的数字个数。

转移如下:

  1. $f_{s,i,j} \leftarrow f_{s,i,j}+ f_{s,i-1,j}$,这个代表第 $i$ 位为 $0$ 的情况。
  2. $f_{s,i,j} \leftarrow f_{s\oplus 2^i,i-1,j-1}$,代表第 $i$ 位为 $1$ 的情况。

另外,初始状态是 $f_{b_i,0,0}\leftarrow f_{b_i,0,0}+1$。

共 318 篇博客