SDUWH 第九届程序设计大赛

A题 数学期望 待补

B题 分块莫队 待补

F题 FFT 待补

I 题 待补

J题 待补

C题:

题目大意:S1和S2都要到T去 最小化修路的费用

分析:也就是尽量能重合走的就重合走 一定存在中间点mid S1到T 和 S2到T 都要经过mid

所以 最小化dis(S1,mid)+dis(S2,mid)+dis(mid,T) 就好

建正边和反边分别跑一次最短路就好

注意!!!一定不要再用spfa 他死了!!!!但是比赛就是用的spfa坑死了

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
using PLI = pair<LL, int>;
const LL INF = 1e18;

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	int n, m, s1, s2, t;
	cin >> n >> m >> s1 >> s2 >> t;
	vector<vector<PII>> G(n), rG(n);
	for(int i = 0; i < m; ++ i) {
		int u, v, w;
		cin >> u >> v >> w;
		G[u].push_back({v, w});
		rG[v].push_back({u, w});
	}
	auto dijkstra = [&](int s, vector<vector<PII>> &g) -> vector<LL> {
		vector<bool> vis(n, 0);
        vector<LL> d(n, INF);
        priority_queue<PLI, vector<PLI>, greater<PLI>> Q; 
        d[s] = 0;
        Q.push({0, s});
        while(!Q.empty()) {
            auto u = Q.top().second; Q.pop();            
            if(vis[u]) continue;
            vis[u] = 1;
            for(auto &[v, w] : g[u]) {
                if(d[v] > d[u] + w) {
                    d[v] = d[u] + w;
                    Q.push({d[v], v});
                }
            }
    	}
    	return d;
	};
	auto d1 = dijkstra(s1, G);
	auto d2 = dijkstra(s2, G);
	auto d3 = dijkstra(t, rG);
	LL ans = INF;
    for(int i = 0; i < n; ++ i) 
    	ans = min(ans, d1[i] + d2[i] + d3[i]);
    cout << (ans >= INF ? -1 : ans) << "\n";
	return 0;
}

D题

大意:一棵树 取一个节点就要把子树全部取掉 求取不超过m个节点的最大值

一道非常典型的树上背包问题啊!!!!当时脑子抽了 居然没打出来 好气啊!!!

dp[i,j] 表示以i为根的子树 保留连续的j个最小值

因为要保证连续 所以先设初始值 dp[i,1]=val[i] 这样后面转移保留比1大的数量时候一定都会加上i节点本身

code:

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=505;
ll dp[maxn][maxn],val[maxn],sz[maxn],sum;
vector<int>Q[maxn];
ll n,m;
void dfs(int,int);
void dfs2(int,int);
int main(){
	memset(dp,0x3f,sizeof(dp));
	cin>>n>>m;
	for(int i=1;i<n;i++){
		int aa,bb;
		scanf("%d%d",&aa,&bb);
		Q[aa].push_back(bb);
		Q[bb].push_back(aa);
	}
	for(int i=1;i<=n;i++)scanf("%lld",&val[i]),sum+=val[i];
	dfs(1,1);
	dfs2(1,1);
	if(m>=n)m=n-1;
	cout<<sum-dp[1][n-m]<<endl;
     return 0;
}
void dfs(int u,int fa){
	sz[u]=1;
	for(int i=0;i<Q[u].size();i++){
		int to=Q[u][i];
		if(to==fa)continue;
		dfs(to,u);
		sz[u]+=sz[to];
	}
}
void dfs2(int u,int fa){
	dp[u][1]=val[u];
	for(int i=0;i<Q[u].size();i++){
		int to=Q[u][i];
		if(to==fa)continue;
		dfs2(to,u);
		for(ll S=n-m;S>0;S--)
		for(int k=0;k<=min(sz[to],S);k++)
		dp[u][S]=min(dp[to][k]+dp[u][S-k],dp[u][S]);
	}
}

E题

大意:一个序列 给定一个最大值max 最小值min 求有多少子序列满足最大值为max 最小值为min

分析:这应该算一个非常经典的问题

考虑容斥 设函数 calc(l,r) 统计都数值都在 内的区间个数。

calc函数还是很好算 直接线性扫一遍即可

经典容斥 :ans=calc(l,r)- calc(l+1,r) - calc(l,r-1) +calc(l+1,r-1)

code:

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 200010;
 
LL n, a[N];
LL ans;
 
void solve(LL x, LL y, LL t) {
	int lst = 1;
	for(int i = 1; i <= n; ++ i) {
    	if(a[i] > x or a[i] < y) {
    		lst = i + 1;
    	}
    	ans += (i - lst + 1) * t;
    }
}
 
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    LL x, y;
    cin >> n >> x >> y;
    for(int i = 1; i <= n; ++ i) cin >> a[i];
    solve(x, y, 1);
    solve(x - 1, y, -1);
    solve(x, y + 1, -1);
    solve(x - 1, y + 1, 1);
    cout << ans << "\n";
    return 0;
}

G题

大意:一个字符串 求每个子序列的值 定义一个子序列的值为不同字符的个数

分析:
也是一道非常经典的计数问题

很明显考虑每个字符单独的贡献为多少

贡献:

左边界在上一个相同的该字母之后,在它之前(左开右闭)

右边界在它之后(闭区间)

和之前有个算有多少个子序列乘积为0一模一样!!!!!!!!!!!!

但是当时比赛就是没想出来唉 这是一个模型 一定要记住!!!!

这样只需要记录一下last数组就好了

code:

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 200010;
 
LL n, a[N];
LL ans;
 
void solve(LL x, LL y, LL t) {
	int lst = 1;
	for(int i = 1; i <= n; ++ i) {
    	if(a[i] > x or a[i] < y) {
    		lst = i + 1;
    	}
    	ans += (i - lst + 1) * t;
    }
}
 
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    LL x, y;
    cin >> n >> x >> y;
    for(int i = 1; i <= n; ++ i) cin >> a[i];
    solve(x, y, 1);
    solve(x - 1, y, -1);
    solve(x, y + 1, -1);
    solve(x - 1, y + 1, 1);
    cout << ans << "\n";
    return 0;
}

H题

大意:一个01矩阵 对角线反转 行反转 列反转 问能否经过一系列操作将矩阵全部转化为0 并记录过程

分析:

首先不需要最优化步骤

每行或者每列要么反转一次 要么不反转

对角线特殊判断一下

默认第一行不反转,那么每一个1所在的列一定是翻转了。检查每一行是否能够不导致冲突。

有方案的话把记录下来的内容输出。

#include <bits/stdc++.h>
using namespace std;

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	int T;
	cin >> T;
	while(T -- ) {
		int n;
		cin >> n;
		vector<vector<int>> A(n, vector<int> (n));
		for(int i = 0; i < n; ++ i) 
			for(int j = 0; j < n; ++ j)
				cin >> A[i][j];
		vector<int> row, col;
		auto solve = [&]() -> bool {
			row.clear(); 
			col.clear();
			for(int i = 1; i < n; ++ i) {
				int c = 0;
				for(int j = 0; j < n; ++ j)
					c += (A[i][j] ^ A[0][j]);
				if(c > 0 and c < n) return 0;
				if(c == n) row.push_back(i);
			}
			for(int i = 0; i < n; ++ i)
				if(A[0][i]) col.push_back(i);
			return 1;
		};
		if(solve()) {
			cout << "YES\n";
			cout << row.size() + col.size() << "\n";
			for(int &i : row) cout << "row " << i + 1 << "\n";
			for(int &i : col) cout << "col " << i + 1 << "\n";
			continue;
		};
		for(int i = 0; i < n; ++ i) A[i][i] ^= 1;
		if(solve()) {
			cout << "YES\n";
			cout << row.size() + col.size() + 1 << "\n";
			cout << "diagonal\n";
			for(int &i : row) cout << "row " << i + 1 << "\n";
			for(int &i : col) cout << "col " << i + 1 << "\n";
			continue;
		};
		cout << "NO\n";
	}
	return 0;
} 

K题:打表找规律

L题:模拟多项式除法

posted @ 2022-07-14 11:29  wzx_believer  阅读(56)  评论(0)    收藏  举报