241114 noip 模拟赛

省流:\(90 + 100 + 20 + 10\)。t1t2花太久时间了。

T1

题意:给一张 \(n \times m\) 的网格图,\((x,y)\)\((x + 1,y)\) 的边为 \(a_x + b_y\)\((x,y)\)\((x,y + 1)\) 的边为 \(c_x + d_y\)。求这张图的最小生成树的边权和。

\(n,m \leq 10^6\)

稍微画图注意到,一个点一定跟它的上面和左边的其中一个点之间的边是最小生成树的边。

一列一列的做,这样 \(a,c\) 就是固定的了,那么如果向上的边小于向左的边,则 \(a + b < c + d\),移项得 \(a - c < d - b\),由于不管在那一列,同一行的 \(d - b\) 固定,所以可以直接按照 \(d - b\) 排序,二分找出 \(a - c\) 的分界点,一部分为向上的边,一部分为向左的边,前缀和优化即可。

代码:

#include<bits/stdc++.h>
#define int __int128
using namespace std;
const int N=1e6+5;
int n,m,a[N],b[N],c[N],d[N],pre[N],suf[N],ans=0;
struct node {
	int w,a,b;
	bool operator<(const node &o) const {return w<o.w;}
}x[N];
int read() {
	int x=0;
	char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x;
}
void write(int x) {
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
signed main() {
	freopen("mst.in","r",stdin);
	freopen("mst.out","w",stdout);
	n=read(),m=read();
	for(int i=1; i<n; i++) a[i]=read();
	for(int i=1; i<=m; i++) b[i]=read();
	for(int i=1; i<=n; i++) c[i]=read();
	for(int i=1; i<m; i++) d[i]=read();
	for(int i=1; i<=n-1; i++) ans+=b[1]+a[i];
	for(int i=1; i<=m-1; i++) ans+=c[1]+d[i];
	for(int i=1; i<m; i++) x[i]=(node){d[i]-b[i+1],d[i],b[i+1]};
	sort(x+1,x+m);
	for(int i=1; i<m; i++) pre[i]=pre[i-1]+x[i].a;
	for(int i=m-1; i>=1; i--) suf[i]=suf[i+1]+x[i].b;
	for(int i=1; i<n; i++) {
		int tmp=a[i]-c[i+1];
		int l=1,r=m-1,pos=m;
		while(l<=r) {
			int mid=l+r>>1;
			if(x[mid].w>=tmp) r=mid-1,pos=mid;
			else l=mid+1;
		}
		ans+=(pos-1)*c[i+1]+pre[pos-1]+(m-pos)*a[i]+suf[pos];
	}
	write(ans);
	return 0;
}

闲话:会炸 long long 也是逆天了。赛时写了 \(90\) 分的部分分而不是写挂/kx

T2

题意:定义一个序列的价值为这个序列本质不同的子序列的个数,给定一个长度为 \(n\) 的序列,求这个序列的所有非空子序列的价值和,对 \(998244353\) 取模。注意子序列是可以不连续的。

\(n \leq 10^6\)

官方做法:

计算一个序列的本质不同子序列数量显然应该使用子序列自动机,即 \(f_i\) 表示最后一个数为 \(i\) 的本质不同子序列数量,在序列末尾加入 \(x\) 时的转移为 \(f_x = 1 + \sum_{i = 1}^n f_i​\)

然后考虑原问题,转化为原序列的每个元素有 \(\frac{1}{2}\)​ 的概率加入计算贡献的序列末尾,上述转移发生与不发生的概率各为 \(\frac{1}{2}\)​,那么此时的转移即为 \(fx=\frac{1}{2}(1+f_x+\sum_{i = 1}^n f_i)\)

最后输出 \(2^n \sum_{i = 1}^n f_i\)​ 即可。

我的赛时做法:

感觉不太好描述,太蒻了,给个代码。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,p=998244353;
int n,a[N],pw[N],dp[N],lst[N],sum[N];
void init(int lim) {
	pw[0]=1;
	for(int i=1; i<=lim; i++) pw[i]=pw[i-1]*2%p;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>n;
	init(n);
	for(int i=1; i<=n; i++) cin>>a[i];
	dp[0]=1;
	for(int i=1; i<=n; i++) {
		if(lst[a[i]]) sum[a[i]]=sum[a[i]]*pw[i-lst[a[i]]-1]%p;
		dp[i]=(dp[i-1]*3%p-sum[a[i]]+p)%p;
		sum[a[i]]=(sum[a[i]]+dp[i-1])%p;
		lst[a[i]]=i;
	}
	cout<<(dp[n]-pw[n]+p)%p;
	return 0;
}

闲话:这题赛时卡我好久。

T3

原题:AT_kupc2018_h。

题意:给定正整数 \(n,m,s\)\(m\) 个三元组 \((l_i,r_i,k_i)\),你需要求出有多少长度为 \(n\) 的序列 \(a_1,a_2,\cdots,a_n\) 满足以下条件:

  • \(\forall 1 \leq i \leq n,1 \leq a_i \leq s\)
  • 对于第 \(i(1 \leq i \leq m)\) 个三元组,\(a_{l_i},a_{l_i + 1},\cdots,a_{r_i}\) 不全等于 \(k_i\)

输出结果对 \(998244353\) 取模的结果。

\(1 \leq n,m,s \leq 2 \times 10^5\)

首先可以考虑对于每种颜色拆开来做,对于一种颜色,如果一个区间包含了另一个区间,则大的区间可以舍去,容易证明。把限制挂在右端点。

我们记 \(f_i\) 表示只考虑 \(m\) 个限制中 \(r \leq i\) 的限制,在 \([1,i]\) 中填数的合法方案数,\(g_i\) 表示只考虑 \(r < i\) 的限制合法且只考虑 \(r \leq i\) 不合法的填数方案数。由于我们已经舍去了包含的方案,所以对于一个 \(i\) 只会有一个限制挂在这里,设这个限制为 \([l,i]\),那么有 \(g_i = f_{l - 1}\),但是这样会算重,若有一个区间 \([l1,r1]\) 满足 \(r1 \geq l\),那么 \([l1,i]\) 都填为当前做的元素会在 \(r1,i\) 都被算一次,于是正确的应该是 \(g_i = f_{l - 1} - \sum_{l \leq j < i} g_j\)。这个容易用二分 + 前缀和优化。

于是对于每种颜色算出他们各自的 \(g_i\) 把他们加在一起就是整体的 \(g_i\) 了,\(f_i = f_{i - 1} \times s - g_i\)

答案即为 \(f_n\)

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,p=998244353;
int n,m,s,f[N],g[N];
vector<int> id[N],sum[N];
vector<pair<int,int>> col[N],ve[N];
signed main() {
	cin>>n>>m>>s;
	for(int i=1; i<=m; i++) {
		int l,r,k;
		cin>>l>>r>>k;
		col[k].push_back(make_pair(l,r));
	}
	for(int i=1; i<=s; i++) {
		if(col[i].empty()) continue;
		sort(col[i].begin(),col[i].end(),[](pair<int,int> &a,pair<int,int> &b) {
			if(a.first==b.first) return a.second>b.second;
			else return a.first<b.first;
		});
		int mn=n+1;
		for(int j=col[i].size()-1; j>=0; j--) {
			if(col[i][j].second>=mn) continue;
			mn=col[i][j].second;
			ve[col[i][j].second].push_back(make_pair(col[i][j].first,i));
		}
	}
	f[0]=1;
	for(int i=1; i<=n; i++) {
		for(int j=0; j<ve[i].size(); j++) {
			int fi=ve[i][j].first,se=ve[i][j].second;
			int ans=f[fi-1];
			if(!id[se].empty()) {
				int pos=lower_bound(id[se].begin(),id[se].end(),fi)-id[se].begin()-1;
				ans=(ans-sum[se].back()+(pos>=0?sum[se][pos]:0)+p)%p;
			}
			id[se].push_back(i);
			if(!sum[se].empty()) sum[se].push_back((sum[se].back()+ans)%p);
			else sum[se].push_back(ans);
			g[i]=(g[i]+ans)%p;
		}
		f[i]=(f[i-1]*s%p-g[i]+p)%p;
	}
	cout<<f[n];
	return 0;
}

闲话:只输入 \(n\) 个数能拿 \(90\) /kx。

T4

原题:P5655。

会了。

感觉这篇讲的很好,有一点疑惑的地方,不过想想也能理解,就不做解释了。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=305,mod=998244353;
int t,n,a[N],c[N],b[N],s[N];
signed main() {
	cin>>t;
	while(t--) {
		cin>>n;
		for(int i=1; i<=n; i++) cin>>a[i];
		int ans=0;
		for(int i=1; i<=n; i++) {
			b[i]=a[i];s[i]=1;
			for(int j=i-1; j>=1; j--) s[j]=(__int128)s[j+1]*(b[j]%a[i])%a[i];
			int tmp=__gcd(s[1],a[i]);
			for(int j=1; j<i; j++) {
				if(s[j+1]%tmp) {
					int t=__gcd(s[j+1],tmp);
					b[j]/=tmp/t;tmp=t;
				}
			}
			int mul=1;
			for(int j=i; j>=1; j--) {
				mul=mul*(b[j]%mod)%mod;
				ans=(ans+mul)%mod;
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}
posted @ 2024-11-14 20:25  System_Error  阅读(33)  评论(0)    收藏  举报