A.
最优方案中 a n s [ i ] [ j ] ≤ 17 ans[i][j]\leq17 ans[i][j]≤17
所以只要把 ≤ 17 \leq17 ≤17 的边连起来就好了
单次 dijkstra \text{dijkstra} dijkstra 时间复杂度 O ( ( n + m ) log n ) O((n+m)\log n) O((n+m)logn) 。
这道题如果把堆去掉换成队列的话时间复杂度是 O ( 17 n 2 ) O(17n^2) O(17n2) 。
#include<bits/stdc++.h>
#define db double
#define ll long long
#define mkp make_pair
#define pii pair<int,int>
#define inf 0x3f3f3f3f
#define fi first
#define se second
using namespace std;
const int Maxn=2000*2000+5;
const int mod=998244353;
int P,t,ans[2005][2005],vis[2005][2005];
ll val[Maxn],res;
priority_queue<pii> q;
int has(int i,int j) {
return (i-1)*(P-1)+j-1;
}
bool solve(int st) {
for(int i=1;i<P;i++) ans[st][i]=inf,vis[st][i]=0;
while(q.size()) q.pop();
int cnt=P-1;
q.push(mkp(0,st));
ans[st][st]=0;
while(q.size()) {
int x=q.top().se,tmp=ans[st][x]; q.pop();
if(vis[st][x]) continue;
vis[st][x]=1;
cnt--;
if(cnt==0) return 1;
for(int j=max(1,x-20);j<=min(P-1,x+20);j++) {
int nx=x*j%P;
if(ans[st][x]+abs(x-j)<ans[st][nx]) {
ans[st][nx]=ans[st][x]+abs(x-j);
q.push(mkp(-ans[st][nx],nx));
}
}
}
return 0;
}
int main() {
memset(ans,0x3f,sizeof(ans));
scanf("%d%d",&P,&t);
val[0]=1;
for(int i=1;i<(P-1)*(P-1);i++) {
val[i]=val[i-1]*t%mod;
}
for(int i=1;i<P;i++) {
solve(i);
for(int j=1;j<P;j++) {
res=(res+ans[i][j]*val[has(i,j)]%mod)%mod;
}
}
printf("%lld",res);
}
B.
考虑区间 dp 。
dp 转移非常显然。
但是你会被卡常数。
有一个结论:区间的第一次操作一定是区间最大值。
考虑怎么证明它:如果先操作 max ,再操作 x ,代价为 L + R + L1 + R1 ;如果先操作 x ,那么代价为 Max + R1 + L + L1 ,显然先操作最大值比较优。
根据 不等式传递性 ,我们每次交换操作序列中 Max 到左边会使答案更优,结论得证。(我们得到了本质上的最优策略 233)
加上这个剪枝,时间复杂度 O ( n 3 ) O(n^3) O(n3) 。(虽然复杂度没变 233)
#include<bits/stdc++.h>
#define db double
#define ll long long
#define mkp make_pair
#define pii pair<int,int>
#define inf 0x3f3f3f3f
#define fi first
#define se second
using namespace std;
const int Maxn=2005;
const int mod=998244353;
//结论:最优决策一定是区间 [l,r] 的最大值
int n,Log[Maxn],a[Maxn],st[Maxn][20],dp[Maxn][Maxn];
ll dp2[Maxn][Maxn],c[Maxn][Maxn];
int ask(int l,int r) {
if(l>r) return 0;
int k=Log[r-l+1];
return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main() {
scanf("%d",&n);
for(int i=2;i<=n;i++) Log[i]=Log[i/2]+1;
for(int i=1;i<=n;i++) scanf("%d",&a[i]),st[i][0]=a[i];
for(int j=1;j<20;j++) {
for(int i=1;i<=n-(1<<j)+1;i++) {
st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
}
for(int i=0;i<=n;i++) c[i][0]=1;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
}
}
for(int i=1;i<=n;i++) dp[i][i]=0,dp2[i][i]=1;
for(int i=1;i<=n+1;i++) dp2[i][i-1]=1;
for(int len=2;len<=n;len++) {
for(int i=1;i<=n-len+1;i++) {
int j=i+len-1;
dp[i][j]=inf;
for(int k=i;k<=j;k++) {
if(a[k]!=ask(i,j)) continue;
if(dp[i][j]==ask(i,k-1)+ask(k+1,j)+dp[i][k-1]+dp[k+1][j]) {
dp2[i][j]=(dp2[i][j]+dp2[i][k-1]*dp2[k+1][j]%mod*c[j-i][k-i]%mod)%mod;
}
else if(dp[i][j]>ask(i,k-1)+ask(k+1,j)+dp[i][k-1]+dp[k+1][j]) {
dp[i][j]=ask(i,k-1)+ask(k+1,j)+dp[i][k-1]+dp[k+1][j];
dp2[i][j]=dp2[i][k-1]*dp2[k+1][j]%mod*c[j-i][k-i]%mod;
}
}
}
}
printf("%lld",dp2[1][n]);
}
C.
非常恶心的模拟题。
很容易看出来答案是若干个等差数列求和。
暴力枚举 i ∈ [ 2 t , 2 t + 1 ) i\in [2^t,2^{t+1}) i∈[2t,2t+1) ,首先不难推出 i ∗ ( c − 1 ) m o d 2 t + 1 = 0 i*(c-1)\mod 2^{t+1} = 0 i∗(c−1)mod2t+1=0
把 c − 1 c-1 c−1 进行 2 2 2 的因数分解,即: i m o d 2 g = 0 i\mod 2^{g}=0 imod2g=0 ,其中 g = max ( 0 , t + 1 − p ) g=\max(0,t+1-p) g=max(0,t+1−p)
这里不难看出两件事:
- 每 2 g 2^g 2g 个数的函数值相同,一共有 2 t 2^t 2t 个数,所以有 2 g − t 2^{g-t} 2g−t 组,而每一组之间的公差为 2 g 2^g 2g
- 如果 i < n − 1 i<n-1 i<n−1 的话这个组可以恰好分完
我们回顾一下等差数列求和公式:首项 * 项数 + 项数 * (项数-1) /2 * 公差
带入上式可以得到答案。
现在我们来解决 i = n − 1 i=n-1 i=n−1 的情况。
我们分为散块和整块。
散块比较简单,只要能整除 2 g 2^g 2g 部分的都是整块(换句话说 1 + 前 2~n-g 位)
后面 n-g+1~n 位就都是散块了。由于我们把最后这部分散块看成了整块,所以多算了 2 g 2^g 2g - sk - 1 个数 ,末项又可以用 首项 + (项数-1) * 公差 来求得,所以我们就做完了。时间复杂度是 O(n) 的。
总结:这道题纯用了数学方法来推导,每一个部分的求和都特别清晰,必须对题目性质有较深刻的认识才能做得出来。
#include<bits/stdc++.h>
#define db double
#define ll long long
#define mkp make_pair
#define pii pair<int,int>
#define inf 0x3f3f3f3f
#define fi first
#define se second
using namespace std;
const int mod=998244353;
const int Maxn=1e7+5;
char s[Maxn];
ll n,m,p,c,res,ksm[Maxn];
//考虑数列 dp(i) 到底长什么样子
//i \in [2^p,2^{p+1})
//i * (c-1) mod 2^p = 0
//显然当 i = 2^p 是恒成立的,此时 dp(i) = 2^p
//如果 c-1 是 2^p 的倍数 ,这一段就是等差数列求和
//
ll calc(ll sx,ll gc,ll xs) {
return (sx*xs%mod+xs*(xs-1)/2%mod*gc%mod)%mod;
}
int main() {
// freopen("data.in","r",stdin);
ksm[0]=1;
for(int i=1;i<=1e7;i++) ksm[i]=ksm[i-1]*2%mod;
int T;
scanf("%d",&T);
while(T--) {
scanf("%s%lld",s+1,&c),n=strlen(s+1);
// reverse(s+1,s+1+n);
int tmp=0;
for(int i=1;i<=n;i++) {
tmp=tmp*2+s[i]-'0';
}
// printf("%d\n",tmp);
c--;
if(c&1) {
printf("0\n");
continue;
}
if(c==0) {
ll tmp=0;
for(int i=1;i<=n;i++) {
tmp=(tmp*2+s[i]-'0')%mod;
}
printf("%lld\n",tmp*(tmp+1)/2%mod);
continue;
}
p=0;
res=0;
while(c%2==0) p++,c/=2;
for(int i=0;i<n;i++) {
//等差数列三要素:首项,公差,项数
//首项 * 项数 + 项数 * (项数-1) /2 * 公差
//首项 = 2^i
if(i<n-1) {
ll sx=ksm[i],gc=max(i+1-p,0ll),xs=ksm[i-gc];
//公差 = 2^{max(i+1-p,0)}
//项数 = 2^{i-gc}
res=(res+ksm[gc]*calc(sx,ksm[gc],xs)%mod)%mod;
}
else {
//项数可以取模哟
ll sx=ksm[i],gc=max(i+1-p,0ll),xs=0,sk=0,mx=0;
for(int j=1;j<=n-gc;j++) {
xs=(xs*2+s[j]-'0')%mod;
}
xs=(xs-ksm[i-gc]+1+mod)%mod;
//计算整块
// printf("%d %d %d %d\n",sx,ksm[gc],xs,gc);
res=(res+ksm[gc]*calc(sx,ksm[gc],xs)%mod)%mod;
//最后一项被多记数了 2^g - sk - 1次
for(int j=n-gc+1;j<=n;j++) {
sk=(sk*2+s[j]-'0')%mod;
}
mx=(sx+(xs-1)*ksm[gc]%mod)%mod;
res=(res-(ksm[gc]-sk-1+mod)%mod*mx%mod+mod)%mod;
//计算散块
// res=(res+sk*mx%mod)%mod;
}
// printf("%d\n",res);
}
printf("%lld\n",res);
}
}
D.
二维 st 表 + bitset + 二分。
做法很显然就不说了。主要是观察到 a [ i ] [ j ] ≤ 100 a[i][j]\leq100 a[i][j]≤100 所以直接上 bitset \text{bitset} bitset (真可谓暴力神器)
时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn) 。(可能常数略大 233)
#include<bits/stdc++.h>
#define db double
#define ll long long
#define mkp make_pair
#define pii pair<int,int>
#define inf 0x3f3f3f3f
#define fi first
#define se second
using namespace std;
const int Maxn=1505;
//二维 st 表 + bitset
int n,m,Log[Maxn],a[Maxn][Maxn],k,res;
bitset<101> st[Maxn][Maxn][11];
inline int read() {
int x=0,f=1; char c=getchar();
while(c<'0'||c>'9') {
c=getchar();
}
while(c>='0'&&c<='9') {
x=(x<<1)+(x<<3)+c-'0';
c=getchar();
}
return x;
}
int qry(int x,int y,int d) {
int k=Log[d];
return (st[x][y][k]|st[x+d-(1<<k)][y][k]|st[x][y+d-(1<<k)][k]|st[x+d-(1<<k)][y+d-(1<<k)][k]).count();
}
int solve(int x,int y,int k) {
if(k==0) return 0;
int l=1,r=min(n-x+1,m-y+1),res=0;
while(l<=r) {
int mid=l+r>>1;
if(qry(x,y,mid)<=k) res=mid,l=mid+1;
else r=mid-1;
}
return res;
}
int main() {
n=read(),m=read(),k=read();
for(int i=2;i<=max(n,m);i++) {
Log[i]=Log[i/2]+1;
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
scanf("%d",&a[i][j]);
st[i][j][0][a[i][j]]=1;
}
}
for(int k=1;k<=10;k++) {
for(int i=1;i<=n-(1<<k)+1;i++) {
for(int j=1;j<=m-(1<<k)+1;j++) {
st[i][j][k]=st[i][j][k-1]|st[i+(1<<k-1)][j][k-1]|st[i][j+(1<<k-1)][k-1]|st[i+(1<<k-1)][j+(1<<k-1)][k-1];
}
}
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
int tmp=solve(i,j,k);
if(!tmp||qry(i,j,tmp)!=k) continue;
res+=tmp-solve(i,j,k-1);
}
}
printf("%d",res);
}