The 2024 Shanghai Collegiate Programming Contest 题解

https://codeforces.com/gym/105229

A无线网络整点栅格统计

思路:可以考虑枚举正方形的任意两个点,如果已知两个正方形的点是一定可以推出另外两个点的,那么判断这两个点是否在范围内。时间复杂度

void solve()
{
    int n,m;
    cin>>n>>m;
    vector a(n + 1 , vector<int>(m + 1));
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            for(int x=0;x<=n;x++){
                for(int y=0;y<=m;y++){
                    int x1=i,y1=j,x2=x,y2=y;
                    int c1 = x2-x1, c2 = y2-y1;
                    int x3=x1+c2, y3=y1-c1;
                    int x4=x3+c1, y4=y3+c2;
                    if(x3>=0 && x3<=n && y3>=0 && y3<=m && x4>=0 && x4<=n && y4>=0 && y4<=m) a[i][j]++;
                }
            }
        }
    }
    for(int i = 0 ; i <= n ; i ++)
        for(int j = 0 ; j <= m ; j ++)
        {
            cout << a[i][j] - 1 << " \n"[j == m];
        }
} 

D咸鱼跑酷

思路:先考虑暴力,从左往右选择更优的选项。可以发现当人数超过一定的数的时候,用乘(不为1)一定更优。所以可以先暴力将人数取到一个较大的值,然后查询后面的累乘值(如果乘为1则变为加,可以考虑用权值线段树维护加乘信息)。

#include <bits/stdc++.h>

using namespace std;

#define all(c) (c).begin(), (c).end()
#define rall(x) (x).rbegin(), (x).rend()
#define Sum(a) accumulate((a).begin(), (a).end() , 0ll)
#define Min(a) *std::min_element((a).begin(), (a).end())
#define Max(a) *std::max_element((a).begin(), (a).end())
#define rev(a) reverse((a).begin(), (a).end())
#define each(x, a) for(auto& x : a)
#define mst(a, x) memset(a, x, sizeof(a))
#define LL long long
#define rep(i, from, to) for(ll i = from;i<to;i++)
#define rrep(i, from, to) for(ll i = from;i>to;i--)
#define to_uni(a) a.erase(unique(begin(a), end(a)), end(a))
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define pp pop_back
#define endl "\n"
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int mod = 1e9 + 7;
const int P = 998244353;
const int INF = 0x3f3f3f3f;
const int dx[4]={1, 0, -1, 0}, dy[4]={0, 1, 0, -1};
const int fx[8] = {-1, -1, 0, 1, 1, 1, 0, -1}, fy[8] = {0, 1, 1, 1, 0, -1, -1, -1};
#define int long long

void solve()
{
	int n;
	cin >> n;
	vector<int> add(n + 1) , mul(n + 1 , 1);
	for(int i = 1 ; i <= n ; i ++)
	{
		for(int j = 0 ; j < 2 ; j ++)
		{
			string s;
			cin >> s;
			int v = stoi(s.substr(1));
			if(s[0] == '+') {
				add[i] = max(add[i],v);
			} else {
				mul[i] = max(mul[i],v);
			}
		}
	}
	vector<int> sum(n << 4) , acm(n << 4) ;
	auto update = [&](int u) -> void{
		int ch = u << 1;
		acm[u] = acm[ch] * acm[ch|1] % P;
		sum[u] = (acm[ch|1] * sum[ch] % P + sum[ch|1]) % P;		
	};
	auto build = [&](auto self, int u , int l, int r) -> void{
		if(l == r) {
			acm[u] = mul[l];				
			if(mul[l] == 1) {
				sum[u] = add[l];
			} else {//一个位置只会用一个道具,乘为1的时候才用加
				sum[u] = 0;
			}
			return;
		}
		int mid = l + r >> 1;
		int ch = u << 1;
		self(self,ch,l,mid) , self(self,ch|1,mid+1,r);
		update(u);
	};
	auto query = [&](auto self, int u , int l, int r , int ql , int qr) -> PLL{
		if(l > qr || r < ql) {
			return {1,0};
		}
		if(ql <= l && r <= qr) {
			return {acm[u],sum[u]};
		}
		int mid = l + r >> 1;
		int ch = u << 1;
		PLL tl = self(self,ch,l,mid,ql,qr) , tr = self(self,ch|1,mid+1,r,ql,qr);
		PLL t = {tl.fi * tr.fi % P, (tl.se * tr.fi % P + tr.se) % P};
		return t;
	};
	build(build,1,1,n);
	vector<int> pre(n + 1) , nxt(n + 1,n);
	for(int i = 1 ; i <= n ; i ++) {
		pre[i] = pre[i - 1] + add[i];
	}
	for(int i = n - 1; i >= 1 ; i --) {//记录哪些位置可以乘
		nxt[i] = mul[i] > 1 ? i : nxt[i + 1];
	}
	int inf = 1e9;
	int q;
	cin >> q;
	while(q --) {
		int u,l,r;
		cin >> u >> l >> r;
		if(l == r) {
			cout << max(u * mul[l] , u + add[l]) % P << endl;
			continue;
		}
		while(l < r && u <= inf) {
			int i = min(nxt[l],r);//每次寻找哪个位置有乘
			u += pre[i - 1] - pre[l - 1];//没乘就加
			l = i;
			if(l == r || u > inf) {
				break;
			}
			u = max(u * mul[l] , u + add[l]);//看有乘的位置用哪个更好
			l ++;
		}
		auto [mul,add] = query(query,1,1,n,l,r);
		int ans = (u % P * mul + add) % P;
		cout << ans << endl;
	}
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(0);
    //cout.precision(10); cout.setf(ios::fixed);
    int t = 1;
    // cin >> t;
    while(t--)
    solve();    
    return 0;
}

E无线软件日

思路:统计字母的个数,取最小值。

void solve()
{
    int n;
    cin >> n;
    string s;
    cin >> s;
    array<int,26> cnt{};
    for(auto &c : s) {
        c = tolower(c);
        cnt[c - 'a'] ++;
    }
    cout << min({cnt['s'-'a'],cnt['h'-'a']/2,cnt['a'-'a']/2,cnt['n'-'a'],cnt['g'-'a'],cnt['i'-'a']}) << endl;
}

F羁绊大师

思路:虑羁绊为点,英雄为边,选出 L 个英雄,即 L 条边,考虑这些边导出的子图。因为每个点的度至多为 2 ,完整的图是由环和链组成。为了让导出子图中,有两个度的点尽可能多,应该导出尽可能多的环,以使得链上的边尽可能少。 可以用并查集维护,每次判断英雄的两个羁绊是否在一个集合中,如果是则这个集合可以组成环否则就是链。如果L是小于等于环边总数的时候 。对每个 L,找到最大的环所能组成的 x (这是个背包问题,把环看做物品即可。 注意到物品数量最多只有 n / 3,因此可以跑一个 的背包,注意到这个背包是存在性背包,因此可以用位运算 + bitset 优化到 其中 w 是 32 或 64。 )如果存在则是L否则是L - 1。如果L大于环边总数,则环一定是都选了,考虑选择链的情况:答案就是 L - 选则尽可能少的链的个数。时间复杂度:

pragma GCC optimize(2)
#pragma optimize(3, on)
#pragma GCC optimize("Ofast")

#include<bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 1e5;
void solve()
{
	int n,m;
	cin >> n >> m;
	vector<int> p(m);
	vector<int> sz(m,1);
	for(int i = 0 ; i < m ; i ++) p[i] = i;
	auto find = [&](int x) {
		while(x != p[x]) {
			x = p[x] = p[p[x]];
		}
		return x;
	};
	vector<bool> cyc(m);
	for(int i = 0 ; i < n ; i ++)
	{
		int u,v;
		cin >> u >> v;
		u -- , v --;
		int a = find(u) , b = find(v);
		if(a == b) {
			cyc[a] = true;
		} else {
			p[b] = a;
			sz[a] += sz[b];
		}
	}
	vector<int> x,y;
	for(int i = 0 ; i < m ; i ++)
	{
		if(p[i] == i) {
			if(cyc[i]) {
				x.emplace_back(sz[i]);
			} else {
				y.emplace_back(sz[i] - 1);
			}
		}
	}
	sort(x.begin(),x.end(),greater());
	sort(y.begin(),y.end(),greater());
	int sumx = accumulate(x.begin(),x.end(),0);

	bitset<N+1> dp{};
	dp[0] = 1;
	vector<int> cnt(n + 1);
	for(auto v : x){
		cnt[v] ++;
	}
	for(int v = 1 ; v <= n ; v ++)
	{
		if(cnt[v]) {
			int s = cnt[v];
			for(int k = 1 ; k < s ; k *= 2) {
				dp |= dp << (k * v);
				s -= k;
			}
			dp |= dp << (s * v);
		}
	}

	vector<int> ans(n + 1);
	int sum = sumx;
	int j = 0;
	for(int i = 1 ; i <= n ; i ++) {
		if(i <= sumx) {
			ans[i] = i - !dp[i];
		} else {
			while(sum < i) {
				sum += y[j ++];
			}
			ans[i] = i - j;
		}
	}
	for(int i = 1 ; i <= n ; i ++)
	{
		cout << ans[i] << " ";
	}
}

signed main()
{	
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t = 1;
	// cin >> t;
	while(t--)
		solve();
	return 0;
}

G象棋大师

思路:可以考虑dp,状态可以设计为 表示到达i,j点且马有哪些马存活的方案数。马存活的方案数可以用二进制压缩存储。每一位的表示第i只马是否存活 。状态转移如下:
下一个抵达的点没有马
下一个抵达的点有马且枚举的状态中这匹马存活:
同时需要满足两个条件:枚举当前的状态所有的马不能攻击到i,j且不能攻击到要抵达的点nx,ny
时间复杂度为 。

#include <bits/stdc++.h>

using namespace std;

#define Sum(a) accumulate((a).begin(), (a).end() , 0ll)
#define Min(a) *std::min_element((a).begin(), (a).end())
#define Max(a) *std::max_element((a).begin(), (a).end())
#define LL long long
#define fi first
#define se second
#define pb push_back
#define pp pop_back
// #define endl "\n"
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int mod = 1e9 + 7;
const int P = 998244353;
const int INF = 0x3f3f3f3f;
const int dx[4]={1, 0, -1, 0}, dy[4]={0, 1, 0, -1};
const int fx[8] = {-1, -1, 0, 1, 1, 1, 0, -1}, fy[8] = {0, 1, 1, 1, 0, -1, -1, -1};
#define int long long
const int N = 1 << 11;
int dp[110][110][N];
void solve()
{
	int n , m;
	cin >> n >> m;
	vector<PII> atk(m);
	map<pair<int,int>,int> id;
	int st = 0;
	for(int i = 0 ; i < m ; i ++)
	{
		int x,y;
		cin >> x >> y;
		atk[i] = {x,y};
		id[make_pair(x,y)] = i;
		st |= (1 << i);
	}
	dp[0][0][st] = 1;
	auto check = [&](int x , int y , int k) -> bool{
		bool ok = true;
		for(auto [u,v] : atk) {
			if(ok == false) break;
			if((k >> id[make_pair(u,v)] & 1) == 0) {
				continue;
			}
			if(!id.count(make_pair(u+1,v)) || (id.count(make_pair(u+1,v)) && (k >> id[make_pair(u+1,v)] & 1) == 0)) {
				if(x == u + 2 && y == v - 1) {
					ok = false;
				}
				if(x == u + 2 && y == v + 1) {
					ok = false;
				}
			}
			if(!id.count(make_pair(u-1,v)) || (id.count(make_pair(u-1,v)) && (k >> id[make_pair(u-1,v)] & 1) == 0)) {
				if(x == u - 2 && y == v - 1) {
					ok = false;
				}
				if(x == u - 2 && y == v + 1) {
					ok = false;
				}
			}
			if(!id.count(make_pair(u,v+1)) || (id.count(make_pair(u,v+1)) && (k >> id[make_pair(u,v+1)] & 1) == 0)) {
				if(x == u - 1 && y == v + 2) {
					ok = false;
				}
				if(x == u + 1 && y == v + 2) {
					ok = false;
				}
			}
			if(!id.count(make_pair(u,v-1)) || (id.count(make_pair(u,v-1)) && (k >> id[make_pair(u,v-1)] & 1) == 0)) {
				if(x == u - 1 && y == v - 2) {
					ok = false;
				}
				if(x == u + 1 && y == v - 2) {
					ok = false;
				}
			}
		}
		return ok;
	};
	for(int k = st ; k >= 0 ; k --) {
		for(int i = 0 ; i <= n ; i ++)
			for(int j = 0 ; j <= n ; j ++)
			{
			    if(!dp[i][j][k]) continue;
				for(auto [x,y] : {make_pair(1,0),make_pair(0,1)})
				{
					int nx = i + x , ny = j + y;
					if(nx > n || ny > n) {
						continue;
					}
					if(!check(nx,ny,k) || !check(i,j,k) ) {
						continue;
					}					
					if(id.count(make_pair(nx,ny)) && (k >> id[make_pair(nx,ny)] & 1) == 1) {
						// cout << bitset<10>(k) << " ";
						// cout << bitset<10>((~(1 << id[make_pair(nx,ny)]))) << endl;
						dp[nx][ny][k & (~(1 << id[make_pair(nx,ny)]))] = (dp[nx][ny][k & (~(1 << id[make_pair(nx,ny)]))] + dp[i][j][k]) % P;
					}
					if(!id.count(make_pair(nx,ny))) {
						// cout << nx << " " << ny << " " << k << endl;
						dp[nx][ny][k] = (dp[nx][ny][k] + dp[i][j][k]) % P;
					}
				}
			}
	}
	int ans = 0;
	for(int k = st ; k >= 0 ; k --)
	{
		ans = (ans + dp[n][n][k]) %P;
	}
	cout << ans << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    //cout.precision(10); cout.setf(ios::fixed);
    int t = 1;
    //cin >> t;
    while(t--)
    solve();    

    return 0;
}

J极简合数序列

思路:先预处理跑一变质数筛,暴力枚举区间个数,求区间内的和判断是否为非质数。时间复杂度

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
vector<int> pri;
bool st[N];

void solve()
{
	int n;
	cin >> n;
	vector<int> a(n + 1);
	for(int i = 1 ; i <= n ; i ++)
	{
		cin >> a[i];
	}
	for(int k = 0 ; k <= n ; k ++)
	{
		for(int i = 1 ; i <= n ; i ++)
		{
			int s = 0;
			for(int j = i ; j - i <= k && j <= n; j ++)
			{
				s += a[j];
			}
			if(st[s]) {
				cout << k << endl;
				return ;
			}
		}
	}
	cout << -1 << endl;
}

signed main()
{	
	for(int i = 2 ; i < N - 2 ; i ++)
	{
		if(!st[i]) pri.emplace_back(i);
		for(auto p : pri) {
			if(p * i >= N) break;
			st[p * i] = true;
			if(p % i == 0) break;
		} 
	}
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t = 1;
	cin >> t;
	while(t--)
		solve();
	return 0;
}

K时光

思路:从题目中可以贪心的认为优先选大的,因为一个方案中如果有若干个数,肯定是大的在前面。n是30可以考虑折半搜索。可以倒着做,先从小到大搜索一半。另一半进行二分查找。另外需要注意的是,需要记录选择的个数,因为贡献是和个数有关系,第二次搜索中二分完后需要再对答案乘上个数。时间复杂度

#define int long long
void solve()
{
	int n,m;
	cin >> n >> m;
	vector<PLL> c(n);
	for(int i = 0 ; i < n ; i ++) cin >> c[i].se;
	for(int i = 0 ; i < n ; i ++) cin >> c[i].fi;
	sort(c.begin(),c.end());
	int half = n >> 1;
	int ans = 0;
	vector<vector<PLL>> f(half + 1);
	auto dfs = [&](auto self, int u , int tim , int cost , int cnt) -> void{
		if(u == half) {
			f[cnt].emplace_back(tim,cost);
			return;
		}		
		self(self,u + 1,tim,cost,cnt);
		if(tim + c[u].se <= m) {
			self(self,u + 1,tim + c[u].se , cost + cnt * c[u].fi , cnt + 1);
		}
	};
	dfs(dfs,0,0,0,0);
	vector<vector<PLL>> unif(half + 1);
	for(int i = 0 ; i <= half ; i ++) {
		sort(f[i].begin(),f[i].end());
		for(auto [x,y] : f[i]) {
			if(unif[i].empty() || unif[i].back().se < y) {
                         //如果贡献不比上一个大并且时间还花的多就不需要
				unif[i].emplace_back(x,y);
			}
		}
	}
	auto dfs2 = [&](auto self, int u , int tim , int cost , int sum , int cnt) -> void{
		if(u == n) {
			for(int c = 0 ; c <= half ; c ++) {
				auto it = lower_bound(unif[c].begin(),unif[c].end(),make_pair(m - tim + 1,0ll));
				if(it == unif[c].begin()) {
					continue;
				}
				it --;
				ans = max(ans , cost + c * sum + it->second);
			}
			return;
		}
		self(self,u + 1,tim,cost,sum,cnt);
		if(tim + c[u].se <= m) {
			self(self,u + 1,tim + c[u].se , cost + cnt * c[u].fi , sum + c[u].fi, cnt + 1);
		}
	};
	dfs2(dfs2,half,0,0,0,0);
	cout << ans << endl;
}

L扩散模型

思路:要求从根到给出点需要固定路径的最小数量是多少。考虑用dp解决。dp[u]表示以u为根的子树到达任意目标点的最小代价是多少。可以考虑转移: ,如果u无法到达目标点则置为无穷大。时间复杂度

// #pragma GCC optimize(2)
#pragma optimize(3, on)
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
	int n,m,k;
	cin >> n >> m >> k;
	vector<vector<int>> g(n + 1);
	for(int i = 1 ; i <= n ; i ++)
	{	
		int k;
		cin >> k;
		for(int j = 0 ; j < k ; j ++)
		{
			int x;
			cin >> x;
			g[i].emplace_back(x);
		}		
	}	
	set<PII> card;
	for(int i = 0 ; i < m ; i ++)
	{
		int u,v;
		cin >> u >> v;
		card.emplace(u,v);
	}
	set<int> leaf;
	for(int i = 0 ; i < k ; i ++)
	{
		int x;
		cin >> x;
		leaf.emplace(x);
	}
	vector<int> dp(n + 1 , 1e9);
	auto dfs = [&](auto self , int u) -> void {
		if(!g[u].size()) {
			if(leaf.count(u)) {
				dp[u] = 0;
			} else {
				dp[u] = 1e9;
			}
			return;
		}
		int tol = 1e9;
		int sum = 0;
		for(auto v : g[u])
		{
			self(self,v);
			sum += dp[v];
			if(card.count(make_pair(u,v))) {
				tol = min(dp[v],tol);
			}
		}
		dp[u] = min({dp[u],tol + 1,sum});

	};
	dfs(dfs,1);
	if(dp[1] == 1e9) {
		cout << -1 << endl;
	} else {
		cout << dp[1] << endl;
	}
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    //cout.precision(10); cout.setf(ios::fixed);
    int t = 1;
    cin >> t;
    while(t--)
    	solve();    
    return 0;
}
posted @ 2024-08-31 17:40  LingKerly  阅读(698)  评论(0)    收藏  举报