牛表

题目描述

给出三个整数 \(x, y, P(1 ≤ x, y < P)\)\(P\) 为素数。可以重复对 \(x\) 执行如下操作: 选择一个整数 \(z ∈ [1, P − 1]\),花费 \(∣ x − z ∣\) 的牛币,使得 \(x = x \cdot z \bmod P\)

最小需要花费多少牛币才能使得 \(x = y\)? 设 \(ans(i, j)\) 为 当 \(x = i, y = j\) 时的答案,为了减少输出,你需要输出 \(\sum_{i=1}^{P-1}\sum_{i=1}^{P-1} ans(i,j)\cdot t^{(i-1)\cdot (P-1)+j-1}\bmod 998244353\)

\(2\le P\le 2000,1\le t\le 5000\)

解法

首先可以 \(\mathcal O(n^3)\)\(\rm Floyd\)。打表可知,\(ans(i,j)\le 17\),这就意味着边数不会超过 \(35n\)。用堆优化 \(\rm Dijkstra\) 可以做到 \(\mathcal O(35n^2\log n)\)

不过复杂度可以优化到更优。

还是由于 \(ans(i,j)\le 17\),所以边权范围很小,令其为 \(D\)。可以开 \(D\) 个队列,复杂度是 \(\mathcal O(n^2D)\) 的。这完全等于没讲

考虑 \(\rm Dijkstra\) 需要重复入队和优先队列维护的原因是边权不同,可能原来更劣的解加上新的边权就更优了。开 \(D\) 个队列,哪种边就塞进哪个队列就规避了这个问题。具体更新过程:找到 \(D\) 个队列中当前离 \(s\)(源点)最近的点,枚举 \(2D\) 种边权来扩展,塞入对应边权的队列。

可以证明从队列中取出的点的最短路一定是递增的。可以用归纳法,先开始最短路是递增的,据此可以证明每个队列的值是递增的,反过来就可以证明原命题。

所以每个点只被更新一次(可以被入队多次),只用最优值来更新即可。每个点拓展一次,拓展复杂度 \(\mathcal O(D)\),所以总共是 \(\mathcal O(n^2D)\) 的。

在神仙学弟的代码上改了改:\(\rm Link.\)

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f |= (s=='-');
	while(s>='0' and s<='9')
		x = (x<<1)+(x<<3)+(s^48),
		s = getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-');
		write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

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

const int inf = 5e6;
const int maxn = 2005;
const int mod = 998244353;

int p,t,head[maxn],cnt;
int dis[maxn];
bool vis[maxn];
struct edge {
	int nxt,to,w;
} e[maxn*20];
struct node {
	int u,w;
	node() {}
	node(int U,int W):u(U),w(W) {}
	
	bool operator < (const node &t) const {
		return w>t.w;
	}
};
priority_queue <node> q;

inline int Abs(int x) {
	return x>0?x:-x;
}

inline int inc(int x,int y) {
	return x+y>=mod?x+y-mod:x+y;
}

void addEdge(int u,int v,int w) {
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	e[cnt].w=w;
	head[u]=cnt;
}

void Dijkstra(int x) {
	for(int i=1;i<p;++i)
		dis[i]=0x3f3f3f3f,vis[i]=0;
	q.push(node(x,0));
	dis[x]=0;
	while(!q.empty()) {
		node t=q.top(); q.pop();
		if(vis[t.u] or (t.w^dis[t.u]))
			continue;
		vis[t.u]=1;
		for(int i=head[t.u];i;i=e[i].nxt) {
			int v=e[i].to;
			if(dis[v]>dis[t.u]+e[i].w) {
				dis[v]=dis[t.u]+e[i].w;	
				q.push(node(v,dis[v]));
			}
		}
	}
}

int main() {
	p=read(9),t=read(9);
	for(int i=1;i<p;++i) {
		int l=max(1,i-18);
		int r=min(p-1,i+18);
		for(int j=l;j<=r;++j) 
			addEdge(i,1ll*i*j%p,Abs(i-j));
	}
	int tmp=1,ans=0; 
	for(int i=1;i<p;++i) {
		Dijkstra(i);
		for(int j=1;j<p;++j)
			ans = inc(ans,1ll*dis[j]*tmp%mod),
			tmp = 1ll*tmp*t%mod;
	}
	print(ans,'\n');
	return 0;
}

矩阵学说

题目描述

给定 \(n\times m\) 的矩阵 \(a\)。求其中包含的恰好含 \(k\) 个不同整数个数的正方形。

\(1\le n,m\le 1500,1\le a_i\le 100\)

解法

\(\text{Subtask 1}\)\(n\le 500\)

枚举左上角和正方形边长。用两个 \(\text{long long}\)\(\rm bitset\) 来维护边长增加时添加的数字。

\(\text{Subtask 2}\)\(n\le 1500\)

对于固定的左上角,正方形包含数字种类是单调的嗄!二分边长,可以用二维 \(\rm st\) 表预处理(\(\rm bitset\)),复杂度 \(\mathcal O \left(n^2\log n\cdot \frac{a_i}{w}\right)\)

需要注意的是二维 \(\rm st\) 表只适用于边长相等的情况。

代码

#pragma GCC optimize(2)
#pragma GCC optimize(Ofast)
#include <cstdio>
#define print(x,y) write(x),putchar(y)


template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f |= (s=='-');
	while(s>='0' and s<='9')
		x = (x<<1)+(x<<3)+(s^48),
		s = getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-');
		write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

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

const int maxn = 1505;

int n,m,K,lg[maxn],lim;
bitset <100> f[12][maxn][maxn];

void init() {
	for(int k=1;k<=lg[lim];++k)
		for(int i=1;i+(1<<k)-1<=n;++i)
			for(int j=1;j+(1<<k)-1<=m;++j)
				f[k][i][j] = (
					f[k-1][i][j]|
					f[k-1][i+(1<<k-1)][j]|
					f[k-1][i][j+(1<<k-1)]|
					f[k-1][i+(1<<k-1)][j+(1<<k-1)]
				);
}

int ask(int u,int d,int l,int r) {
	int dis=lg[r-l+1];
	return (
		f[dis][u][l]|
		f[dis][u][r-(1<<dis)+1]|
		f[dis][d-(1<<dis)+1][l]|
		f[dis][d-(1<<dis)+1][r-(1<<dis)+1]
	).count();
}

int calc(int x) {
	int ret=0;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j) {
			int l=0,r=min(n-i,m-j)+1,mid;
			while(l<r) {
				mid=l+r+1>>1;
				if(ask(i,i+mid-1,j,j+mid-1)<=x)
					l=mid;
				else r=mid-1;
			}
			ret += l;
		}
	return ret;
}

int main() {
	n=read(9),m=read(9),K=read(9);
	lim = max(n,m);
	for(int i=2;i<=lim;++i)
		lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			f[0][i][j][read(9)-1]=1;
	init();
	print(calc(K)-calc(K-1),'\n');
	return 0;
}

牛牛和数组操作

题目描述

给定长度为 \(n\) 的数列 \(a\)。你需要做 \(n\) 次操作,每次操作为以下形式:

  • 选择一个整数 \(x\) 满足 \(a_x ≠ 0\),将 \(a_x\) 变成 \(0\),令 \(l,r\) 分别为离 \(x\) 向左/右最近的 \(a\)\(0\) 的下标,此次操作的花费为 \(\max\{a_l, a_{l+1}, . . . , a_{x−1}\} + \max\{a_{x+1}, a_{x+2}, . . . , a_{r}\}\) 牛币。

问有多少不同的操作方式使得操作花费的牛币最少,两种操作不同当且仅当两种操作的操作序列不同。答案对 \(998244353\) 取模。

\(1\le n\le 2000,1\le a_i\le n\)

解法

很难想象正解时间复杂度是 \(\mathcal O(n^3)\)

首先可以发现,一次操作会将一个区间分成互不干扰的两半,这提醒我们区间 \(\mathtt{dp}\)。不过似乎我们并不需要 \(\mathtt{dp}\) 得出最少花费,可以感性理解每次选择区间 \(\max\) 作为划分点是最优的。

合并两个区间时贡献就是 \(dp_{i,k-1}\cdot dp_{k+1,j}\cdot \binom{j-i}{k-i}\),就是两个长度为 \(k-i,j-k\) 的序列保留各自顺序进行合并。

另外讲一个小 \(\rm trick\)?发现区间 \(\max\) 如果相邻,它们的总贡献相当于是其中之一的贡献乘二。感觉没啥用

代码

#pragma GCC optimize(2)
#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f |= (s=='-');
	while(s>='0' and s<='9')
		x = (x<<1)+(x<<3)+(s^48),
		s = getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-');
		write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

const int maxn = 2005;
const int mod = 998244353;

int pos[maxn][maxn];
int n,a[maxn],dp[maxn][maxn];
int fac[maxn],ifac[maxn];

inline int inc(int x,int y) {
	return x+y>=mod?x+y-mod:x+y;
}

inline int inv(int x,int y=mod-2) {
	int r=1;
	while(y) {
		if(y&1) r=1ll*r*x%mod;
		x=1ll*x*x%mod; y>>=1;
	}
	return r;
}

void init() {
	fac[0]=1;
	for(int i=1;i<=n;++i) {
		pos[i][i]=i;
		dp[i][i]=dp[i][i-1]=dp[i+1][i]=1;
		for(int j=i+1;j<=n;++j)
			if(a[pos[i][j-1]]<a[j])
				pos[i][j]=j;
			else pos[i][j]=pos[i][j-1];
		fac[i]=1ll*fac[i-1]*i%mod;
	}
	ifac[n]=inv(fac[n]);
	for(int i=n-1;i>=0;--i)
		ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
}

inline int C(int n,int m) {
	if(n<m or n<0 or m<0)
		return 0;
	return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}

int main() {
	n=read(9);
	for(int i=1;i<=n;++i)
		a[i]=read(9);
	init(); int F;
	for(int len=2;len<=n;++len)
		for(int i=1;i+len-1<=n;++i) {
			int j=i+len-1,mx=a[pos[i][j]];
			for(int k=pos[i][j];k<=j;k=pos[k+1][j]) {
				if(a[k]^mx) break;
				F = (k==i or k==j)?1:C(len-1,k-i);
				dp[i][j] = inc(dp[i][j],1ll*dp[i][k-1]*dp[k+1][j]%mod*F%mod);
				if(k==j) break;
			}
		}
	print(dp[1][n],'\n');
	return 0;

与巨

题目描述

定义无穷序列 \(f\)\(f_1=1,f_n=f_{n-1}\cdot 2+1\),函数 \(G(x)=\min_{f_i\ge x}f_i\)

定义 \(dp_{c,0}=0\),递推式:

\[dp_{c,i}=\max\{dp_{c,i-1},[(ic\ \&\ G(i))=i]\cdot i\} \]

\(\sum_{i=0}^n dp_{c,i}\bmod 998244353\)

\(1\le |n|\le 10^7,1\le c\le 10^{18}\)

解法

\(\text{Subtask 1}\):打表

对于 \(|n|\le 30,1\le c\le 50\)

有一个结论:\(dp_{c,i}\)\(c\) 为偶数时恒为 \(0\)。这里感谢 \(\text{Emm_titan}\) 送出的证明!

  • \(i=0\)。此时值为零。
  • \(i\neq 0\)。找到 \(i\) 最低位的 \(1\) 的位置 \(j\),乘上一个偶数相当于 \(j\) 上的 \(1\) 出现了偶数次!所以第 \(j\) 位就变成 \(0\) 了,而且前面的位都不会更改它了。

因此就只有 \(25\) 个有用的 \(c\)。可以对 \(dp_{c_i,k\cdot 10^6}\) 的前缀和进行打表。由于 \(n\)\(10^9\) 范围,先找到离 \(n\) 最近的 \(k\),再 \(\mathcal O(10^6)\) 地暴算。

\(\text{Subtask 2}\):非打表

我直呼 \(\rm gelivable\)!非常地 \(\rm niubility\)

首先发现 \(G(i)=2^{t+1}-1\)\(t\) 为二进制最高位。那么 \(x\text{ and }G(x)=x\bmod 2^{t+1}\)

所以条件可以转化为 \(i\cdot (c-1)\bmod 2^{t+1}=0\)。将 \(c-1\) 表示为 \(q\cdot 2^p\)\(\gcd(q,2)=1\)),那么 \(i\) 必须被 \(2^{t+1-p}\) 整除。

由于 \(t\) 至多只到 \(|n|-1\),我们枚举 \(t\)

  • \(t<|n|-1\)

    此时 \([2^t,2^{t+1})\) 之内的数都在 \(n\) 之内,令 \(g=t+1-p\),那么 \(i\)\(2^g\) 整除意味着 \(i\) 的低 \(g\) 位全为零。

    那么满足条件的数实际上是 \(2^t,2^t+2^g,...,2^t+k\cdot 2^g\)。其中 \(k=2^{t-g}\)。而且每一项都贡献了 \(2^g\) 次,实际上对于 \(2^t\) 就是 \([2^t,2^t+2^g)\)

  • \(t=|n|-1\)

    和上面类似,只是此时 \(k=\left \lfloor \frac{n}{2^g} \right\rfloor+2^{t-g}\)。最后一项的贡献次数也多算了,需要减去。

代码

咕咕咕…

posted on 2021-10-14 23:06  Oxide  阅读(125)  评论(0编辑  收藏  举报