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;
}

浙公网安备 33010602011771号