ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【题解】2021牛客OI赛前集训营-提高组(第一场)

2021-10-05 15:33:06  阅读:117  来源: 互联网

标签:OI int 题解 st Maxn res mod 集训营 define


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)

这里不难看出两件事:

  1. 每 2 g 2^g 2g 个数的函数值相同,一共有 2 t 2^t 2t 个数,所以有 2 g − t 2^{g-t} 2g−t 组,而每一组之间的公差为 2 g 2^g 2g
  2. 如果 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);
}

标签:OI,int,题解,st,Maxn,res,mod,集训营,define
来源: https://blog.csdn.net/cqbzlydd/article/details/120613702

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有