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,j,k}\) 为考虑加上若干个个值为 \(i\) 的后缀,当前(还没加上值为 \(i\) 的后缀)数字的大小为 \(j\),已经加了 \(k\) 次。(状态中的值都是在模 \(P\) 意义下的)
转移时枚举 \(p=0\sim 8-k\),表示加上 \(p\) 个后缀。记 \(g_i\) 为值为 \(i\) 的后缀的个数。
\(\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;
}

浙公网安备 33010602011771号