2019中国大学生程序设计竞赛(CCPC) - 网络选拔赛
A.^&^
题意:
找到最小的正数\(C\),满足\((A\ xor\ C)\&(B\ xor \ C)\)最小。
思路:
输出\(A\&B\)即可,特判答案为0的情况。
Code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MAXN = 1e5+5,MAXM = 2e5+5,MOD = 1e9+7,INF = 0x3f3f3f3f,N = 1e6+1;
#define lson o<<1,l,m;
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
using namespace std;
int t;
ll A,B;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin>>t;
while(t--){
cin>>A>>B;
cout <<max(1LL,(A&B))<<'\n';
}
return 0;
}
B.array
题意:
给出\(a_1,\cdots, a_n\),满足两两各不相同且不超过\(n\),现有两种操作:
- \((1,pos)\),将\(a_{pos}\)加上\(inf\)。
- \((2,r,k)\),询问超过\(k\)的,且不等于\(a_i,i\leq r\)的最小整数。
思路:
考虑权值线段树,那么对于\(\geq k\)这个条件就很方便处理。
涉及下标,对于每个结点维护权值出现的位置(每个数出现一次),那么对于第二个限制,答案就为:位置大于\(r\)的最小的数。
对于修改操作,直接将位置设置为\(inf\),表示什么时候都可以用,因为无论这样,这个位置上面的权值都是可以使用的。
之后在线段树上面查询即可。
对于第一个条件会划分出\(log\)个区间,每个区间往下又是\(log\),所以会有两个\(log\)。
但其实一个\(log\)即可,我们往下一次之后,就没必要找其它地方了,并且每次优先考虑往左子树走。
Code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MAXN = 1e5+5,MAXM = 2e5+5,MOD = 1e9+7,INF = 0x3f3f3f3f,N = 1e6+1;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
using namespace std;
int t,n,m,a[MAXN],p[MAXN],maxv[MAXN<<2];
inline void pushUp(int o){
maxv[o] = max(maxv[o<<1],maxv[o<<1|1]);
}
void build(int o,int l,int r){
if(l==r){
maxv[o] = p[l];
return ;
}
int m = mid;
build(lson);build(rson);
pushUp(o);
}
void update(int o,int l,int r,int p){
if(l==r){
maxv[o] = INF;
return ;
}
int m = mid;
if(p<=m)update(lson,p);
else update(rson,p);
pushUp(o);
}
int query2(int o,int l,int r,int v){
if(l==r){
if(maxv[o]>v)return l;
else return INF;
}
int m = mid;
if(maxv[o<<1]>v)return query2(lson,v);
else return query2(rson,v);
}
int query(int o,int l,int r,int v,int k){
if(l>=k){
return query2(o,l,r,v);
}
int m = mid;
int ans = query(rson,v,k);
if(k<=m){
ans = min(ans,query(lson,v,k));
}
return ans;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin>>t;
while(t--){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i],p[a[i]]=i;
p[n+1] = INF;
build(1,1,n+1);
int op,r,k,lastans=0;
while(m--){
cin>>op;
if(op==1){
cin>>r;
r^=lastans;
r = a[r];
update(1,1,n+1,r);
}else{
cin>>r>>k;
r^=lastans;k^=lastans;
//cout <<r<<' '<<k<<'\n';
lastans = query(1,1,n+1,r,k);
cout <<lastans<<'\n';
}
}
}
return 0;
}
C.K-th occurrence
题意:
给出一个字符串\(S\),回答多个询问:\((l,r,k)\),即\(s_l\cdots s_r\)第\(k\)次出现的位置。
思路:
对于处理多个相同子串的问题,可以考虑后缀数组。
然后这题挺考察对后缀数组的理解程度的。
- 对于\(s_l\),通过\(Rank[l]\)找到其在\(sa\)数组中的位置\(pos\);
- 通过\(pos\)向前向后找尽可能长的区间,满足\(lcp\geq r-l+1\),那么就知道所有的子串个数且其后缀排名;
- 但这样无法快速知道原串中第\(k\)出现的位置;
- 问题等价于区间查询第\(k\)大;
- 主席树!
- 结点维护原位置信息就行,也就是\(sa\)数组。
稍微有点细节,详见代码:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int T, n, q;
char s[N];
struct SA{ //sa:1...n Rank:0...n-1
int x[N], y[N], sa[N], c[N], height[N], Rank[N];
int f[N][20], lg[N];
int n; //length
void da(char *s, int m){
n++;
for(int i = 0; i < m; i++) c[i] = 0;
for(int i = 0; i < n; i++) c[x[i] = s[i]]++;
for(int i = 1; i < m; i++) c[i] += c[i - 1] ;
for(int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
for(int k = 1; k <= n; k <<= 1) {
int p = 0 ;
for(int i = n - k; i < n; i++) y[p++] = i ;
for(int i = 0; i < n; i++) if(sa[i] >= k) y[p++] =sa[i] - k;
for(int i = 0; i < m; i++) c[i] = 0;
for(int i = 0; i < n; i++) c[x[y[i]]]++;
for(int i = 1; i < m; i++) c[i] += c[i - 1];
for(int i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i] ;
swap(x , y); p = 1; x[sa[0]] = 0;
for(int i = 1; i < n; i++)
x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i-1] + k] == y[sa[i] + k] ? p - 1 : p++;
if(p >= n) break ;
m = p;
}
n--;
int k = 0;
for(int i = 0; i <= n; i++) Rank[sa[i]] = i;
for(int i = 0; i < n; i++) {
if(k) k--;
int j = sa[Rank[i] - 1];
while(s[i + k] == s[j + k]) k++;
height[Rank[i]] = k;
}
}
ll count() {
ll ans = 0;
for(int i = 1; i <= n; i++) ans += n - sa[i] - height[i];
return ans;
}
void init() {
for(int i = 2; i < N; i++) lg[i] = lg[i >> 1] + 1;
for(int i = 2; i <= n; i++) f[i][0] = height[i];
for(int j = 1; j < 20; j++)
for(int i = 2; i + (1 << j) - 1 <= n; i++)
f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]) ;
}
int get_lcp(int l, int r) {
++l; if(l > r) return n + 1;
int k = lg[r - l + 1];
return min(f[l][k], f[r - (1 << k) + 1][k]);
}
}suf;
int rt[N * 22], ls[N * 22], rs[N * 22], sumv[N * 22];
int tot;
void build(int &o, int l, int r) {
o = ++tot;
ls[o] = rs[o] = sumv[o] = 0;
if(l == r) return;
int mid = (l + r) >> 1;
build(ls[o], l, mid); build(rs[o], mid + 1, r);
}
void insert(int &o, int last, int l, int r, int v) {
o = ++tot;
sumv[o] = sumv[last] + 1;
ls[o] = ls[last]; rs[o] = rs[last];
if(l == r) return;
int mid = (l + r) >> 1;
if(v <= mid) insert(ls[o], ls[last], l, mid, v);
else insert(rs[o], rs[last], mid + 1, r, v);
}
int query(int o, int last, int l, int r, int k) {
if(l == r) return l;
int mid = (l + r) >> 1;
int sz = sumv[ls[o]] - sumv[ls[last]];
if(sz >= k) return query(ls[o], ls[last], l, mid, k);
else return query(rs[o], rs[last], mid + 1, r, k - sz);
}
int getl(int p, int sz) {
int l = 1, r = p + 1, mid;
while(l < r) {
mid = (l + r) >> 1;
if(suf.get_lcp(mid, p) >= sz) r = mid;
else l = mid + 1;
}
return l;
}
int getr(int p, int sz) {
int l = p, r = n + 1, mid;
while(l < r) {
mid = (l + r) >> 1;
int tmp = suf.get_lcp(p, mid);
if(suf.get_lcp(p, mid) >= sz) l = mid + 1;
else r = mid;
}
return r - 1;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n >> q;
cin >> s;
suf.n = n;
suf.da(s, 520);
suf.init();
tot = 0;
build(rt[0], 0, n);
for(int i = 1; i <= n; i++) {
insert(rt[i], rt[i - 1], 0, n, suf.sa[i]);
}
while(q--) {
int l, r, k; cin >> l >> r >> k;
--l, --r;
int pos = suf.Rank[l];
int ll = getl(pos, r - l + 1);
int rr = getr(pos, r - l + 1);
if(rr - ll + 1 < k) {
cout << -1 << '\n';
} else {
cout << query(rt[rr], rt[ll - 1], 0, n, k) + 1 << '\n';
}
}
}
return 0;
}
D.path
题意:
给出一个有向图,有\(q\)次询问,对于每次询问,输出其第\(k\)小路径。
\(n,m,q,k\leq 5*10^4\)。
思路:
- 最直接的想法,直接贪心\(bfs\),加入所有边!
- 很轻松就会被hack,比如菊花图。
- 考虑减枝?但会影响正确性...
似乎将上界设松一点可以过 - 是否一次需要加入所有边?显然不是,但肯定要加入最小的边。
- 考虑细化贪心,原来的贪心可能会加入很多没用的。
- 对于条边,扩展两种状态:加上最小的一条边,或者换成次大的边。
这样就能枚举到所有的状态了。跟有一次牛客最小团的一个题比较类似。
Code
#include <bits/stdc++.h>
//#define heyuhhh ok
#define MP make_pair
#define sz(g) (int)g.size()
#define fi first
#define se second
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 5;
int T, n, m, Q;
vector <pii> g[N];
int query[N];
ll res[N];
struct node{
int from, e;
ll dis;
bool operator < (const node &A) const {
return dis > A.dis;
}
};
int main() {
#ifdef heyuhhh
freopen("input.in", "r", stdin);
#else
ios::sync_with_stdio(false); cin.tie(0);
#endif
cin >> T; ;
while(T--) {
cin >> n >> m >> Q;
for(int i = 1; i <= n; i++) g[i].clear();
for(int i = 1; i <= m; i++) {
int u, v, w; cin >> u >> v >> w;
g[u].push_back(MP(v, w));
}
for(int i = 1; i <= n; i++) {
sort(g[i].begin(), g[i].end(), [&](pii A, pii B) {
return A.se < B.se;
});
}
int MAX = 0, cnt = 0;
for(int i = 1; i <= Q; i++) cin >> query[i], MAX = max(MAX, query[i]);
priority_queue <node> q;
for(int i = 1; i <= n; i++) {
if(sz(g[i])) q.push(node{i, 0, g[i][0].se});
}
while(!q.empty()) {
node cur = q.top(); q.pop();
res[++cnt] = cur.dis;
if(cnt > MAX) break;
int u = cur.from, e = cur.e;
if(e + 1 < sz(g[u])) {
q.push(node{u, e + 1, cur.dis - g[u][e].se + g[u][e + 1].se});
}
int v = g[u][e].fi;
if(sz(g[v])) q.push(node{v, 0, cur.dis + g[v][0].se});
}
for(int i = 1; i <= Q; i++) {
cout << res[query[i]] << '\n';
}
}
return 0;
E.huntian oy
题意:
定义\(f(n,a,b)=\sum_{i=1}^n\sum_{j=1}^igcd(i^a-j^a,i^b-j^b)[gcd(i,j)=1]\% 10^9+7\)。
先有\(T\)组询问,每组询问给出\(n,a,b\),求\(f(n,a,b)\)。
思路:
先有一个公式如下:
若\(a\geq b\),且\(a,b\)互质,则有:
我也不知道这咋证= =
反正有了这个式子之后就好推了:
至于为什么后面那部分可以化成这样,这涉及到\(gcd(n,m)=gcd(n-m,n)\),也就是说,与之互素的数是成对出现的,并且其和为\(n\)。当然\(n=1\)的情况除外。
之后式子就可以化为这样:
之后直接上杜教筛就是了。
Code
#include <bits/stdc++.h>
#define heyuhhh ok
using namespace std;
typedef long long ll;
const int N = 5e6 + 5, MOD = 1e9 + 7, inv6 = 166666668;
int p[N];
ll phi[N];
bool chk[N];
unordered_map <int, int> mp;
inline void init() {
phi[1] = 1;
int cnt = 0, k = N - 1, i;
for(i = 2; i <= k; ++i) {
if(!chk[i]) p[++cnt] = i, phi[i] = i - 1;
for(int j = 1; j <= cnt && i * p[j] <= k; j++) {
chk[i * p[j]] = 1;
if(i % p[j] == 0) {phi[i * p[j]] = phi[i] * p[j]; break;}
phi[i * p[j]] = phi[i] * (p[j] - 1);
}
}
for(i = 1; i < N; ++i) {
phi[i] = phi[i - 1] + 1ll * i * phi[i];
if(phi[i] >= MOD) phi[i] %= MOD;
}
}
int djs_phi(int n) {
if(n <= 5000000) return phi[n];
if(mp[n]) return mp[n];
int ans = 1ll * (n + 1) * n % MOD * (2ll * n + 1) % MOD * inv6 % MOD;
for(register int i = 2, j; i <= n; i = j + 1) {
j = n / (n / i);
ans -= ((ll(j - i + 1) * (j + i)) >> 1) % MOD * (ll)djs_phi(n / i) % MOD;
ans %= MOD;
}
if(ans < 0) ans += MOD;
return mp[n] = ans;
}
int n, a, b, T;
int main() {
#ifdef heyuhhh
freopen("input.in", "r", stdin);
#else
ios::sync_with_stdio(false); cin.tie(0);
#endif
init();
cin >> T;
while(T--) {
cin >> n >> a >> b;
int ans = djs_phi(n) - 1;
ans = (1ll * ans * (MOD + 1) / 2) % MOD;
cout << ans << '\n';
}
return 0;
}
思路:
考虑倒序操作即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int n, m;
int a[N], b[N], c[N];
bool vis[N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m;
vector <int> ans;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= m; i++) cin >> b[i];
int now = 0;
for(int i = m; i >= 1; i--) {
if(!vis[b[i]]) {
vis[b[i]] = 1;
c[++now] = b[i];
}
}
for(int i = 1; i <= n; i++) if(!vis[a[i]]) c[++now] = a[i];
for(int i = 1; i <= now; i++) cout << c[i] << ' ';
return 0;
}
G.Windows Of CCPC
没什么好说的,递归处理即可。
Code
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<cassert>
#define REP(r,x,y) for(register int r=(x); r<(y); r++)
#define REPE(r,x,y) for(register int r=(x); r<=(y); r++)
#ifdef sahdsg
#define DBG(...) printf(__VA_ARGS__)
#else
#define DBG(...) void(0)
#endif
using namespace std;
int _s; char _c;
template <class T>
void read(T&x) {
x=0; do _c=getchar(); while(!isdigit(_c) && _c!='-'); _s=1; if(_c=='-') _s=-1, _c=getchar(); while(isdigit(_c)) { x=x*10+_c-'0'; _c=getchar();} x*=_s;
}
typedef long long LL;
#define MAXN 1000007
char ans[15][1028][1028];
int main() {
ans[0][0][0]='C', ans[0][0][1]='C', ans[0][1][0]='P', ans[0][1][1]='C';
REP(r,0,10)
REP(i,0,1<<(r+1)) {
REP(j,0,1<<(r+1)) {
if(ans[r][i][j]=='C') {
ans[r+1][i*2][j*2]='C', ans[r+1][i*2][j*2+1]='C';
ans[r+1][i*2+1][j*2]='P', ans[r+1][i*2+1][j*2+1]='C';
} else {
ans[r+1][i*2][j*2]='P', ans[r+1][i*2][j*2+1]='P';
ans[r+1][i*2+1][j*2]='C', ans[r+1][i*2+1][j*2+1]='P';
}
}
}
int T; read(T);
while(T--) {
int k; read(k); k--;
DBG("!%d\n", k);
REP(i,0,1<<(k+1)) {
REP(j,0,1<<(k+1)){
//assert(ans[k][i][j]>' ');
putchar(ans[k][i][j]);
}
putchar('\n');
}
}
return 0;
}
H.Fishing Master
贪心处理,队友写的,没有看。
反正能不浪费时间就不浪费时间。
Code
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<queue>
#define REP(r,x,y) for(register int r=(x); r<(y); r++)
#define REPE(r,x,y) for(register int r=(x); r<=(y); r++)
#ifdef sahdsg
#define DBG(...) printf(__VA_ARGS__)
#else
#define DBG(...) void(0)
#endif
using namespace std;
int _s; char _c;
template <class T>
void read(T&x) {
x=0; do _c=getchar(); while(!isdigit(_c) && _c!='-'); _s=1; if(_c=='-') _s=-1, _c=getchar(); while(isdigit(_c)) { x=x*10+_c-'0'; _c=getchar();} x*=_s;
}
typedef long long LL;
#define MAXN 100007
struct node {
int v, r;
bool operator<(const node &rt) const {
return v<rt.v;
}
};
priority_queue<node> q;
int t[MAXN];
int main() {
int T; read(T);
while(0<T--) {
while(!q.empty()) q.pop();
int n,k; read(n); read(k);
LL ans=(LL)k*n;
REP(i,0,n) {
read(t[i]);
}
REP(i,0,n) {
ans+=t[i];
q.push((node){min(k,t[i]),t[i]-k});
}
REP(i,1,n) {
node now=q.top(); q.pop();
ans-=now.v;
if(now.r>0)
q.push((node){min(k,now.r),now.r-k});
}
printf("%lld\n", ans);
}
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。