zoukankan      html  css  js  c++  java
  • [Contest on 2021.10.9] 说好的 100kb 呢?

    牛表

    题目描述

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

    (2le Ple 2000,1le tle 5000)

    解法

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

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

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

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

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

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

    在神仙学弟的代码上改了改:( m 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,'
    ');
    	return 0;
    }
    

    矩阵学说

    题目描述

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

    (1le n,mle 1500,1le a_ile 100)

    解法

    ( ext{Subtask 1})(nle 500)

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

    ( ext{Subtask 2})(nle 1500)

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

    需要注意的是二维 ( m 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),'
    ');
    	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) 取模。

    (1le nle 2000,1le a_ile n)

    解法

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

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

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

    另外讲一个小 ( m 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],'
    ');
    	return 0;
    
    

    与巨

    题目描述

    定义无穷序列 (f)(f_1=1,f_n=f_{n-1}cdot 2+1),函数 (G(x)=min_{f_ige 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}mod 998244353)

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

    解法

    ( ext{Subtask 1}):打表

    对于 (|n|le 30,1le cle 50)

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

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

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

    ( ext{Subtask 2}):非打表

    我直呼 ( m gelivable)!非常地 ( m niubility)

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

    所以条件可以转化为 (icdot (c-1)mod 2^{t+1}=0)。将 (c-1) 表示为 (qcdot 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+kcdot 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} ight floor+2^{t-g})。最后一项的贡献次数也多算了,需要减去。

    代码

    咕咕咕…

  • 相关阅读:
    CRL快速开发框架系列教程二(基于Lambda表达式查询)
    CRL快速开发框架系列教程一(Code First数据表不需再关心)
    非关系型数据库来了,CRL快速开发框架升级到版本4
    CRL快速开发框架开源完全转到Github
    火车订票系统的高并发解决方案
    用wordpress搭建个人博客
    centos/redhat安装mysql
    vue之nextTick全面解析
    微信公众号开发笔记3-sdk接入(nodejs)
    微信公众号开发笔记2(nodejs)
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/15386700.html
Copyright © 2011-2022 走看看