2023 CCPC 哈尔滨站题解 更新至 6 题 ( The 9th CCPC (Harbin) Onsite(The 2nd Universal Cup. Stage 10: Harbin))
Preface
这场也是属于是打得有点牢了,一个签到题卡半天。感觉ccpc的题不是那么的合胃口呢,感觉每次做ccpc的签到题就会有种题很简单,但我是傻逼的既视感。
赛后和张神一起又搞了个小模拟题,甚是恶心。然后一看榜单,Ag都没有。ccpc强度这么大的吗
以下是代码火车头:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip>
#include <functional>
#include <random>
#include <bitset>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/priority_queue.hpp>
#define endl '\n'
#define int long long
#define rep(i,a,b) for (int aa = a, bb = b, i = aa; i <= bb; i++)
#define rep2(i,a,b) for (int aa = a, bb = b, i = aa; i >= bb; i--)
namespace pbds = __gnu_pbds;
using namespace std;
template<class Key>
using pbds_set = pbds::tree<Key, pbds::null_type, std::less<Key>,
pbds::rb_tree_tag, pbds::tree_order_statistics_node_update>;
template<class T>
using pbds_queue = pbds::priority_queue<T>;
template<typename T>
using vec = vector<T>;
using PII = pair<int, int>;
using INT = __int128;
template<typename T>
void cc(const vector<T>& tem) {
for (const auto& x : tem) cout << x << ' ';
cout << endl;
}
template<typename... Args>
void cc(const Args &... a) {
size_t idx = 0;
((std::cout << a << (++idx == sizeof...(Args) ? "\n" : " ")), ...);
}
void cc() { cout << endl; }
void fileRead() {
#ifdef LOCALL
freopen("D:\\AADVISE\\cppvscode\\CODE\\in.txt", "r", stdin);
freopen("D:\\AADVISE\\cppvscode\\CODE\\out.txt", "w", stdout);
#endif
}
void kuaidu() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); }
inline int max(int a, int b) {
if (a < b) return b;
return a;
}
inline double max(double a, double b) {
if (a < b) return b;
return a;
}
inline int min(int a, int b) {
if (a < b) return a;
return b;
}
inline double min(double a, double b) {
if (a < b) return a;
return b;
}
void cmax(int& a, const int& b) { if (b > a) a = b; }
void cmin(int& a, const int& b) { if (b < a) a = b; }
void cmin(double& a, const double& b) { if (b < a) a = b; }
void cmax(double& a, const double& b) { if (b > a) a = b; }
int gcd(int a, int b) {
if (!b) return a;
return gcd(b, a % b);
}
template<const int T>
int Kuai(int a, int b) {
int l = 1;
while (b) {
if (b % 2) l = l * a % T;
a = a * a % T, b /= 2;
}
return l;
}
int getb(int i, int j) { return i >> j & 1; }
Problem B. Memory
一个签到题,但是思路歪了。
一开始非常傻傻的交了一发wa了之后开始思考怎么做,觉得可以直接去找当前第\(i\)个位置往前的四五十个位置,只计算这些位置产生的贡献,因为再往前的位置的答案起码会被除个2的几十次方,应该会很大,所以就不需要去考虑了,一交WA3。
正解是因为当答案不再是整数的时候其实那些小数部分并不会对答案产生影响,我们只需要注意整数部分就好了,因为有小数的时候答案就一定不会有0的存在,所以如果我们此时得到的数字大于等于0,就说明是正数,否则就是负数。(等于号是因为向下取整了)
#include <bits/stdc++.h>
using namespace std;
int main()
{
// freopen("in.txt", "rt", stdin);
// freopen("out.txt", "wt", stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin>>n;
int x=0,s=0,fg=0;
for(int i=1;i<=n;i++){
cin>>x;
if(s&1) fg=s>0?1:-1;
s=s/2+x;
if(s==0&&fg==0) cout<<0;
else if(s>=1||(s==0&&fg==1)) cout<<"+";
else cout<<"-";
}
return 0;
}
Problem D. A Simple MST Problem
其实这个题也不是太难,不过前期卡题卡了太久没有搞这个。
首先想着是\(x\)和\(y\)去连边,贡献是他俩的\(lcm\)的对应的\(w\),就是说怎么着都会大于等于\(w_x\),毕竟最小公倍数只会可能比\(x\)大,他的质因数也肯定只会比\(x\)多而不会少,所以我们可以确定,\(x\)如果要连边的话,我们不妨直接让他和一个质数连接在一起,这样的话,产生的贡献就会是\(w_x+1\),而还有一种情况会使得贡献等于\(w_x\),就是\(x\)和\(y\)的质因数都一样或者是包含关系,而这一部分我们如何处理呢?
我们可以用一个数组\(g\)去记录下来一个数字\(x\)的质因数的乘积是多少,然后判断\(x\)和\(y\)的质因数是否有倍数的关系,有倍数的关系,就说明是存在这个包含关系的。
那么我们就可以先把一个区间\(l,r\)之间的\(g\)数字的个数存下来,用\(siz\)表示,\(siz[x]\)代表的是\(g[某一个数]=x\)的个数。
然后我们从小到大枚举\(siz\),如果当前的\(siz[i]\)有值之后,就先加上这个\(i\)产生的贡献,当前的i产生的贡献有所有\(g[_]=i\)的那些点和其中一个点o连接,贡献是\(val[i]*siz[i]-1\),然后还有点o和质数连接的贡献,最后的式子是\(ans+=val[i]*(siz[i]-1)+val[i]+1\)。接下来去枚举i的倍数j,把这些倍数的贡献加上来,这些贡献是\(val[j]*siz[j]\)。
最后要记得减去质数的贡献,因为我们在从小到大枚举siz的时候会把质数的贡献也给算上,但是质数并不应该有贡献,所以要减去一个\(val[p]+1\)。(我们不能直接continue掉质数\(p\)的情况,因为质数\(p\)的倍数我们也需要去枚举)
另外,如果一个区间\(l,r\)里没有质数的话,暴力打个表看一下发现区间并不会很长,所以直接暴力一个最小生成树就好了。
//--------------------------------------------------------------------------------
const int N = 1e6 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
class FENJIE {
int mul(int a, int b, int m) {
return static_cast<__int128>(a) * b % m;
}
int power(int a, int b, int m) {
int res = 1 % m;
for (; b; b >>= 1, a = mul(a, a, m))
if (b & 1) res = mul(res, a, m);
return res;
}
vector<int> ddd(vector<PII> tem) {
vector<int> p;
int l = 1;
function<void(int)> f = [&](int n) {
if (n == tem.size()) {
p.push_back(l);
return;
}
if (tem[n].second) {
tem[n].second -= 1;
l *= tem[n].first;
f(n);
l /= tem[n].first;
tem[n].second += 1;
}
f(n + 1);
};
f(0);
sort(p.begin(), p.end());
return p;
}
public:
//求出一个数所有的质因数
vector<int> factorize(int n) {
vector<int> p;
function<void(int)> f = [&](int n) {
if (n <= 10000) {
for (int i = 2; i * i <= n; ++i)
for (; n % i == 0; n /= i)
p.push_back(i);
if (n > 1) p.push_back(n);
return;
}
if (isprime(n)) {
p.push_back(n);
return;
}
auto g = [&](int x) { return (mul(x, x, n) + 1) % n; };
int x0 = 2;
while (true) {
int x = x0, y = x0, d = 1, power = 1, lam = 0, v = 1;
while (d == 1) {
y = g(y), ++lam, v = mul(v, abs(x - y), n);
if (lam % 127 == 0) { d = gcd(v, n), v = 1; }
if (power == lam) { x = y, power *= 2, lam = 0, d = gcd(v, n), v = 1; }
}
if (d != n) {
f(d), f(n / d);
return;
}
++x0;
}
};
f(n);
sort(p.begin(), p.end());
return p;
}
//判断一个数是不是质数
bool isprime(int n) {
if (n < 2) return false;
static constexpr int A[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23 };
int s = __builtin_ctzll(n - 1);
int d = (n - 1) >> s;
for (auto a : A) {
if (a == n) return true;
int x = power(a, d, n);
if (x == 1 || x == n - 1) continue;
bool ok = false;
for (int i = 0; i < s - 1; ++i) {
x = mul(x, x, n);
if (x == n - 1) {
ok = true;
break;
}
}
if (!ok) return false;
}
return true;
}
//求出一个数的质因数,PII形式,代表质因数和指数
vector<PII> zhiyinshu(int n) {
vector<int> tem = factorize(n);
vector<PII> tmp;
tem.push_back(-1);
int las = 0, cnt = 0;
for (auto x : tem) {
if (x != las) {
if (las)tmp.push_back({ las, cnt });
cnt = 1, las = x;
}
else cnt++;
}
return tmp;
}
//求出一个数的所有的因数
vector<int> factor(int n) { return ddd(zhiyinshu(n)); }
} fj;
class DSU {
struct Info {
int fa;
int siz;
} dsu[N];
public:
Info& operator()(const int& x) { return dsu[find(x)]; }
Info& operator[](const int& x) { return dsu[x]; }
void clear(int n) {
//TODO 初始化
rep(i, 1, n) dsu[i].fa = i, dsu[i].siz = 1;
}
void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
dsu[y].fa = dsu[x].fa;
if (dsu[x].siz < dsu[y].siz) swap(dsu[x], dsu[y]);
dsu[y].fa = dsu[x].fa, dsu[x].siz += dsu[y].siz;
//TODO 合并操作
}
int find(int x) { return x == dsu[x].fa ? x : dsu[x].fa = find(dsu[x].fa); }
bool same(int x, int y) { return (find(x) == find(y)); }
} dsu;
//--------------------------------------------------------------------------------
bool pri[N], fl[N];
int val[N], g[N];
vec<int> bei[N];
void pre() {
rep(i, 2, N - 1) g[i] = 1;
rep(i, 2, N - 1) {
if (fl[i]) continue;
pri[i] = 1;
val[i]++;
g[i] = i;
for (int j = i + i; j < N; j += i) fl[j] = 1, val[j]++, g[j] *= i;
}
}
int siz[N];
bool vis[N];
int solve1(int l, int r, int p) {
// cc(1111);
rep(i, 0, r) siz[i] = 0, vis[i] = 0;
rep(i, l, r) {
siz[g[i]]++;
// cc(i, g[i]);
}
int ans = 0;
rep(i, 2, r) {
if (vis[i] || (siz[i] == 0)) continue;
// cc(i);
vis[i] = 1;
// if (i != p)
ans += (siz[i] - 1) * val[i] + val[i] + 1;
for (int j = i + i; j <= r; j += i) {
if (vis[j] || (siz[j] == 0)) continue;
vis[j] = 1;
ans += val[j] * (siz[j]);
}
}
ans -= val[p] + 1;
return ans;
}
int solve2(int l, int r) {
dsu.clear(r);
vec<arr(int, 3)> ed;
rep(i, l, r) {
rep(j, i + 1, r) {
ed.push_back({ i,j,(int)fj.zhiyinshu(i * j / gcd(i,j)).size() });
}
}
sort(ed.begin(), ed.end(), [](arr(int, 3) a, arr(int, 3) b) {
return a[2] < b[2];
});
int cnt = 0, ans = 0;;
for (auto& [x, y, v] : ed) {
if (cnt >= r - l + 1 - 1) break;
if (dsu.same(x, y)) continue;
cnt++;
dsu.merge(x, y);
ans += v;
}
return ans;
}
signed main() {
fileRead();
kuaidu();
pre();
T = 1;
cin >> T;
while (T--) {
int l, r;
cin >> l >> r;
if (l == 1) {
int ans = 0;
rep(i, 2, r) {
ans += val[i];
}
cc(ans);
continue;
}
int fl = 0;
rep(i, l, r) {
if (pri[i]) {
fl = i;
break;
}
}
if (fl) {
cc(solve1(l, r, fl));
}
else {
cc(solve2(l, r));
}
}
return 0;
}
Problem G. The Only Way to the Destination
一个有点ex的题,我们首先知道的是不能出现\(2*2\)的空地,如果出现了那么就是NO,那么什么样的情况是允许的呢?我们考虑一个情况,如果我们全部处理出来了空地的数据,并且处理出来了空地之间谁和谁应该连边,那么我们可以用并查集判断答案。如果连边的时候冲突了,那么就是NO。否则就是YES。
我们在处理的时候,如果墙的y差的大于2了,那么就说明他们直接可以产生一个\(2*2\)的空地,那么就不行,所以有一个小判断。根据这个判断我们可以节省掉很多的情况。
所以我们处理出来的空地应该是和墙是一个量级的。另外连边的时候用一个并查集,如果连边失败,那么直接输出NO就可以了,也不需要接着往后连边了,最多也就会连\(n-1\)的边,所以对他使用模拟吧。
这种题写的真的很ex,和张神从读题到写完差不多花了快2h的时间,吐
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N=400005;
ll n,m;
int k;
struct WALL{
ll x1,x2,y;
bool operator<(const WALL w)const{
return y<w.y||(y==w.y&&x1<w.x1);
}
}w[N];
int fa[N];
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
vector<WALL>emp;
vector<ll>ys;
bitset<N>haswall;
bool cover(WALL a,WALL b){
ll x1=a.x1; ll x2=a.x2;
ll x3=b.x1; ll x4=b.x2;
ll len=min(x2,x4)-max(x1,x3)+1;
if(len>=2){
//cout<<x1<<' '<<x2<<' '<<x3<<' '<<x4<<' '<<len<<endl;
cout<<"NO"<<endl;
exit(0);
}
if(len>=1)return true;
else return false;
}
void solve(void){
cin>>n>>m>>k;
for(int i=1;i<=k;++i){
cin>>w[i].x1>>w[i].x2>>w[i].y;
ys.push_back(w[i].y);
}
if(n==1||m==1){
cout<<"YES"<<endl;
return;
}
ys.push_back(0);
ys.push_back(m+1);
sort(ys.begin(),ys.end());
for(int i=1;i<ys.size()-1;++i){
if(ys[i]-ys[i-1]>2){
cout<<"NO"<<endl;
return;
}
}
for(int i=1;i<=k;++i)haswall[w[i].y]=1;
for(int i=1;i<=m;++i)if(!haswall[i])emp.push_back({1,n,i});
sort(w+1,w+k+1);
for(int i=1;i<=k;++i){
int j=i;
int y=w[i].y;
for(;j+1<=k&&w[j+1].y==y;++j);
int lst=1;
for(int l=i;l<=j;++l){
if(w[l].x1>lst)emp.push_back({lst,w[l].x1-1,y});
lst=w[l].x2+1;
}
if(n>=lst)emp.push_back({lst,n,y});
i=j;
}
int siz=emp.size();
sort(emp.begin(),emp.end());
for(int i=1;i<=siz;++i)fa[i]=i;
int i,j;
int lsti=0,lstj;
for(lstj=0;lstj+1<siz&&emp[lstj+1].y==emp[0].y;++lstj);
i=lstj+1;
for(int i=0;i<siz;++i){
//cout<<emp[i].x1<<' '<<emp[i].x2<<' '<<emp[i].y<<endl;
}
for(;i<siz;++i){
j=i;
for(;emp[j+1].y==emp[i].y&&j+1<siz;++j);
//cout<<"ij "<<i<<' '<<j<<endl;
int l=lsti,r=lsti-1;
for(int p=i;p<=j;++p){
for(;emp[l].x2<emp[p].x1&&l<=lstj;++l);
for(;emp[r+1].x1<=emp[p].x2&&r<lstj;++r);
//cout<<"plr "<<p<<' '<<l<<' '<<r<<endl;
for(int pp=l;pp<=r;++pp){
if(!cover(emp[p],emp[pp]))continue;
//cout<<"connect "<<p<<' '<<pp<<endl;
int fx=find(p);
int fy=find(pp);
if(fx==fy){
cout<<"NO"<<endl;
return;
}
fa[fx]=fy;
}
}
lsti=i;
lstj=j;
i=j;
}
cout<<"YES"<<endl;
return;
}
signed main(void){
ios::sync_with_stdio(false),cin.tie(0);
//int T; cin>>T; while(T--)
solve();
return 0;
}
Problem J. Game on a Forest
有一个知识的前提,就是SG函数。非0的时候就是必胜,否则就是必败。我们可以考虑枚举去掉的边和去掉的点,然后判断剩下的这个图的必胜态和必败态。那么一个森林的SG函数呢?我们可以先考虑一个简单的树对应的SG函数是多少,然后很多的树就直接SG函数异或起来就好了,这就是森林的SG函数了。
单个树的SG函数呢?通过打表得知,设点数是0的树的SG函数是0,然后发现点的个数是奇数的sg是1,点的个数是偶数的话就是2。
然后随后就写一个判断去掉某一个边或者去掉某一个点的情况就好了。这里我们可以用一个dfs函数,然后判断当前点去掉的情况可以算出来,(就像树形dp的那种感觉差不多),然后再判断一下去掉这个点与父节点连接的时候(也就是去掉边)的情况,算一下贡献就好了。
#include<bits/stdc++.h>
#define PII pair<int,int>
#define endl '\n'
#define ll long long
using namespace std;
const int N=100005;
const ll mod=1e9+7;
int n,m;
vector<int>to[N];
int cnt_type;
int root[N];
int type[N];
int siz[N];
int tot;
int ans;
void dfs(int u,int fa){
siz[u]=1;
type[u]=cnt_type;
for(int v:to[u]){
if(v==fa)continue;
dfs(v,u);
siz[u]+=siz[v];
}
}
int sg(int x){
return (x%2==0)?2:1;
}
void count(int u,int fa){
int tmp;
for(int v:to[u]){//del edge
if(v==fa)continue;
tmp=tot^sg(siz[root[type[u]]]);
tmp^=sg(siz[v]);
tmp^=sg(siz[root[type[u]]]-siz[v]);
if(!tmp){
//cout<<"ok "<<u<<' '<<v<<endl;
ans++;
}
}
tmp=tot^sg(siz[root[type[u]]]);
for(int v:to[u]){//del node
if(v==fa){
tmp^=sg(siz[root[type[u]]]-siz[u]);
}else{
tmp^=sg(siz[v]);
}
}
if(!tmp){
//cout<<"ok "<<u<<endl;
ans++;
}
for(int v:to[u]){
if(v==fa)continue;
count(v,u);
}
}
void solve(){
cin>>n>>m;
for(int i=1;i<=m;++i){
int x,y; cin>>x>>y;
to[x].push_back(y); to[y].push_back(x);
}
for(int i=1;i<=n;++i){
if(!type[i]){
++cnt_type;
dfs(i,0);
root[cnt_type]=i;
}
}
for(int i=1;i<=cnt_type;++i)tot^=sg(siz[root[i]]);
for(int i=1;i<=cnt_type;++i)count(root[i],0);
for(int i=1;i<=cnt_type;++i){
//cout<<root[i]<<endl;
}
cout<<ans<<endl;
}
signed main(void){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//int T; cin>>T; while(T--)
solve();
return 0;
}
Problem L. Palm Island
一个很难评的题,我们假设b数组是一个1,2,3的这样的排列,这样比较好说一点。
然后我们就直接对数组a进行操作,让第一个数字放2(一直使用操作1),最后一个数字放1(一直使用操作2),然后接着第一个数字放3,最后一个数字放2,...,然后就好了。
但是一开始写了一个比较麻烦的写法,就是让第一个数字和第二个数字放1,2,然后再放2,3,其实做法没啥区别,但是当时脑子瓦特了搞了个复杂的而且还得判断的写法,只能说自己吃屎吃多了,脑子都不正常了。
签到题开的还是慢。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e3+5;
const int m = 1e6+5;
int a[N], b[N],n;
void op1(){
int t=a[1];
for(int i=2;i<=n;i++) a[i-1]=a[i];
a[n]=t;
}
void op2(){
int t=a[2];
for(int i=2;i<n;i++) a[i]=a[i+1];
a[n]=t;
}
int main()
{
// freopen("in.txt", "rt", stdin);
// freopen("out.txt", "wt", stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--){
cin>>n;
vector<int> ans;
for(int i = 1; i <=n; i++) cin>>a[i];
for(int i = 1; i <=n; i++) cin>>b[i];
for(int i = 2;i<=n;i++){
while(a[1]!=b[i]) op1(),ans.push_back(1);
while(a[n]!=b[i-1]) op2(),ans.push_back(2);
}
while(a[1]!=b[1]) op1(),ans.push_back(1);
for(auto x:ans) cout<<x;
cout<<endl;
}
return 0;
}
Problem M. Painter
一个小模拟题,我们直接先预处理出来最后被渲染的点集有谁,然后我们再对于那些画圆画矩形的操作挨个去枚举点集里的每一个点就好了。
不用担心爆掉,毕竟渲染的点集的大小总和有限制,具体的时间复杂度没有细算,虽然开了个map存点集,但反正不会爆掉(bushi
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
map<PII, char> mp;
int A[N][7];
char C[N];
bool inCir(int x, int y, int r, int x0, int y0) {
return (x0 - x) * (x0 - x) + (y0 - y) * (y0 - y) <= r * r;
}
bool inRec(int x1, int y1, int x2, int y2, int x0, int y0) {
if (x0<x1 or x0>x2) return 0;
if (y0<y1 or y0>y2) return 0;
return 1;
}
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
cin >> n;
rep(i, 1, n) {
string s; cin >> s;
int a, b, c, d, e;
char q1, q2;
if (s == "Circle") {
cin >> a >> b >> c >> q1;
A[i][1] = 1;
A[i][2] = a;
A[i][3] = b;
A[i][4] = c;
C[i] = q1;
// cout << (char)d << endl;
}
else if (s == "Rectangle") {
cin >> a >> b >> c >> d >> q1;
A[i][1] = 2;
A[i][2] = a;
A[i][3] = b;
A[i][4] = c;
A[i][5] = d;
// A[i][6] = e;
C[i] = q1;
// cout << (char)e << endl;
}
else {
cin >> a >> b >> c >> d;
A[i][1] = 0;
A[i][2] = a;
A[i][3] = b;
A[i][4] = c;
A[i][5] = d;
rep(ii, a, c) rep(jj, b, d) {
mp[{ii, jj}] = '.';
}
}
}
rep(i, 1, n) {
if (A[i][1] == 0) {
int a, b, c, d;
a = A[i][2];
b = A[i][3];
c = A[i][4];
d = A[i][5];
rep2(jj, d, b) {
rep(ii, a, c) {
cout << mp[{ii, jj}];
}
cc();
}
continue;
}
if (A[i][1] == 1) {
for (auto [p, val] : mp) {
auto [x, y] = p;
if (inCir(A[i][2], A[i][3], A[i][4], x, y)) {
mp[p] = C[i];
}
}
}
else {
for (auto [p, val] : mp) {
auto [x, y] = p;
if (inRec(A[i][2], A[i][3], A[i][4], A[i][5], x, y)) {
mp[p] = C[i];
}
}
}
}
}
return 0;
}
PostScript
这周末要去郑州了,可以先回一趟菏泽回家躺个一个晚上,真爽。
昨晚做了个梦,梦见郑州打铁了,但愿不要发生阿巴阿巴

浙公网安备 33010602011771号