Loading

20260330 紫题训练

P2481 [SDOI2010] 代码拍卖会

题目大意:

给出 \(n,P\),问有多少个不含 \(0\)\(n\) 位正整数满足数位上的数字从左到右不降且是 \(P\) 的倍数(\(n\le 10^{18},P\le 500\))。

由于要求不降,可以发现至多只有 \(8\) 个交界处,除交界处以外全为一个数字。

重新看填数字的过程,可以看作先给全部位设为 \(1\) 然后可以选一个后缀给每个数位加 \(1\)(即设成 \(2\),再加 \(1\) 就是设为 \(3\)),这个后缀加 \(1\) 操作可以做 \(8\) 次。

但如果从每一次操作给后缀加的角度考虑,要限定每次选后缀的长度不降以防止算重,这样状态是 \(\mathcal O(nP)\) 的。

赛时想法

\(h(i)=\underbrace{11\dots 1}_{i\text{个}1}\)

可以设 \(f_{i,j,k}\) 为处理完 \(i\) 次加 \(1\),值模 \(P\)\(j\),最后一次选后缀的长度为 \(k\) 的方案数。

转移枚举 \(p=k\sim n\)

\[f_{i+1,(j+h(p))\%P,p}\gets f_{i+1,(j+h(p))\%P,p}+f_{i,j,k}(k\le p) \]

由于不区分顺序,可以从考虑给后缀加的值上来考虑。

\(f_{i,j,k}\) 为考虑加上若干个个值为 \(i\) 的后缀,当前(还没加上值为 \(i\) 的后缀)数字的大小为 \(j\),已经加了 \(k\) 次。(状态中的值都是在模 \(P\) 意义下的)

转移时枚举 \(p=0\sim 8-k\),表示加上 \(p\) 个后缀。记 \(g_i\) 为值为 \(i\) 的后缀的个数。

\[f_{i+1,(j+i*p)\%P,k+p}\gets f_{i+1,(j+i*p)\%P,k+p}+\binom{g_i+t-1}{t}f_{i,j,k} \]

\(\dbinom{a+b-1}{b}\) 是在 \(a\) 个元素的集合内选出 \(b\) 个元素(可重复)的方案数,用隔板法即可证明。

\(h(i)=\underbrace{11\dots 1}_{i\text{个}1}\)

由定义显然有 \(h(i)=10h(i-1)+1\)

由于 \(h(i)\%P\) 只有 \(P\) 种取值,所以它的循环节长度是 \(\mathcal O(P)\) 的。

处理 \(g\) 数组时可以先暴力求 \(h\),找到循环节后重复加上循环次数倍的循环节中元素的值,具体见代码。

#include<bits/stdc++.h>
#define N 505
using namespace std;
using ll=long long;
ll n,g[N],f[N][N][9];
const int P=999911659;
int p,ans,val[N],vis[N];
const int inv[]={0,1,499955830,666607773,
249977915,199982332,833259716,571378091,624944787};
int C(ll x,int y){
	int res=1;
	for(ll i=x-y+1;i<=x;i++) res=1ll*res*(i%P)%P;
	for(int i=1;i<=y;i++) res=1ll*res*inv[i]%P;
	return res;
}
int main(){
	scanf("%lld%d",&n,&p);
	memset(vis,-1,sizeof vis);
	for(int i=1,t=p>1?1:0;i<=n;i++,t=(t*10+1)%p){
		if(~vis[t]){
			n-=i-1;ll v=n/(i-vis[t]);
			for(int j=vis[t];j<i;j++)
				g[val[j]]+=v;n%=i-vis[t];
			for(int j=0;j<n;j++) g[val[j+vis[t]]]++;
			f[0][val[n?n+vis[t]-1:i-1]][0]=1;break;
		}g[val[i]=t]++,vis[t]=i;
		if(i==n) f[0][t][0]=1;
	}
	for(int i=0;i<p;i++)
		for(int j=0;j<p;j++)
			for(int k=0;k<9;k++)
				for(int t=0;t+k<9;t++)
					(f[i+1][(j+i*t)%p][k+t]+=f[i][j][k]*C(g[i]+t-1,t))%=P;
	for(int i=0;i<9;i++) (ans+=f[p][0][i])%=P;
	printf("%d",ans);
	return 0;
}

P3350 [ZJOI2016] 旅行者

题目就是网格图多组询问最短路。

假设我们当前要处理起始点和终点都在 \((x_l,y_l)\)\((x_r,y_r)\) 的矩形内的询问。

考虑分治,设 \(mid=\lfloor\dfrac{x_l+x_r}{2}\rfloor\),枚举 \(i=y_l\sim y_r\)

分别求出以 \((mid,i)\) 作为源点到矩形内每个点的最短路。设到 \((x,y)\) 的最短距离为 \(dis_{x,y}\),对于每个询问 \((x_1,y_1),(x_2,y_2)\)\(dis_{x_1,y_1}+dis_{x_2,y_2}\) 更新答案。

然后在 \(x\) 轴沿 \(mid\) 分开成两个矩形,将不跨越边界的询问放到两个矩形内分治。

为什么是对的?

跨过边界的询问由最短路的性质可知一定求出了最小值,而其它询问会被分治下去,一定会变成第一种情况。

但如果 \(y\) 的范围很大而 \(x\) 的范围小的话时间复杂度不对,可以使用形如 KD-Tree 的方式,每次沿长边分开。

#include<bits/stdc++.h>
#define N 100005
#define inf 0x3f3f3f3f
using namespace std;
struct Point{
	int x,y;
	bool operator<(const Point &t)const{
		return y>t.y;
	}
}a[N],b[N];
struct edge{Point x;int w;};
bitset<N>vis;
vector<edge>s[N];
priority_queue<Point>q;
int n,m,Q,tot,dis[N],ans[N];
int id(Point v){return (v.x-1)*m+v.y;}
void add(int x,int y,int u,int v,int w){
	s[id({x,y})].push_back({{u,v},w});
	s[id({u,v})].push_back({{x,y},w});
}
bool check(Point v,int xl,int yl,int xr,int yr){
	return xl<=v.x&&yl<=v.y&&xr>=v.x&&yr>=v.y;
}
void dij(int xl,int yl,int xr,int yr,int st){
	for(int i=xl;i<=xr;i++)
		for(int j=yl;j<=yr;j++)
			dis[id({i,j})]=inf,vis[id({i,j})]=false;
	q.push({st,dis[st]=0});
	while(!q.empty()){
		auto t=q.top();q.pop();
		if(vis[t.x]) continue;vis[t.x]=true;
		for(auto p:s[t.x]){int Id=id(p.x);
			if(check(p.x,xl,yl,xr,yr)&&dis[Id]>dis[t.x]+p.w)
				q.push({Id,dis[Id]=dis[t.x]+p.w});
		}
	}
}
void solve(int xl,int yl,int xr,int yr,vector<int>&c){
	if(xl==xr&&yl==yr){
		for(auto p:c) ans[p]=0;return;
	}vector<int>l,r;
	if(xl+yr<yl+xr){
		int mid=xl+xr>>1;
		for(int i=yl;i<=yr;i++){
			dij(xl,yl,xr,yr,id({mid,i}));
			for(auto p:c) ans[p]=min(ans[p],dis[id(a[p])]+dis[id(b[p])]);
		}
        for(auto p:c){
			if(max(a[p].x,b[p].x)<=mid) l.emplace_back(p);
			if(min(a[p].x,b[p].x)>mid) r.emplace_back(p);
		}
		solve(xl,yl,mid,yr,l);
		solve(mid+1,yl,xr,yr,r);
	}
	else{
		int mid=yl+yr>>1;
		for(int i=xl;i<=xr;i++){
			dij(xl,yl,xr,yr,id({i,mid}));
			for(auto p:c) ans[p]=min(ans[p],dis[id(a[p])]+dis[id(b[p])]);
		}
        for(auto p:c){
            if(max(a[p].y,b[p].y)<=mid) l.emplace_back(p);
            if(min(a[p].y,b[p].y)>mid) r.emplace_back(p);
        }
		solve(xl,yl,xr,mid,l);
		solve(xl,mid+1,xr,yr,r);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) for(int j=1,x;j<m;j++) scanf("%d",&x),add(i,j,i,j+1,x);
	for(int i=1;i<n;i++) for(int j=1,x;j<=m;j++) scanf("%d",&x),add(i,j,i+1,j,x);
	scanf("%d",&Q);vector<int>tmp;
	for(int i=1;i<=Q;i++) tmp.emplace_back(i),
		scanf("%d%d%d%d",&a[i].x,&a[i].y,&b[i].x,&b[i].y);
	memset(ans,0x3f,sizeof ans),solve(1,1,n,m,tmp);
	for(int i=1;i<=Q;i++) printf("%d\n",ans[i]);
	return 0;
}
posted @ 2026-03-30 16:39  Jokersen  阅读(10)  评论(0)    收藏  举报