2019 Multi-University Training Contest 9
B
题意:在 n*m 的矩形上有 k 个平行于坐标轴的射线,求把矩形分成多少块。 \(n,m \le 10^9,K \le 10^5\)
key:线段树
V-E+F=2,即计算点数和边数,用个线段树扫一下。细节麻烦。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
typedef pair<LL,LL> pll;
const int SZ = 1e6 + 10;
const int INF = 1e9 + 10;
const int mod = 1e9 + 7;
const LD eps = 1e-8;
LL read() {
LL n = 0;
char a = getchar();
bool flag = 0;
while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
if(flag) n = -n;
return n;
}
struct BIT {
int tree[SZ],n;
void init(int _n) {
n = _n;
n ++;
for(int i = 1;i <= n;i ++) tree[i] = 0;
}
void add(int x,int d) {
x ++;
for(int i = x;i <= n;i += i & -i) tree[i] += d;
}
int ask(int x) {
x ++;
int ans = 0;
for(int i = x;i > 0;i -= i & -i) ans += tree[i];
return ans;
}
int ask(int l,int r) {
return ask(r)-ask(l-1);
}
}tb;
pii X[SZ],Y[SZ];
vector<int> del[SZ],add[SZ];
void work(pii X[],pii Y[],int n,int m,LL &V,LL &E) {
// puts("-------------------");
for(int i = 0;i <= m;i ++) del[i].clear(),add[i].clear();
for(int i = 1;i <= n;i ++) {
if(X[i].first < X[i].second) {
del[X[i].first].push_back(i);
add[X[i].second].push_back(i);
}
}
tb.init(n);
for(int i = 1;i <= n;i ++) {
if(X[i].first >= 1) {
tb.add(i,1);
}
}
for(int i = 1;i <= m;i ++) {
sort(add[i].begin(),add[i].end());
sort(del[i].begin(),del[i].end());
for(int x : add[i]) tb.add(x,1);
if(Y[i].first < Y[i].second) {
if(Y[i].first >= 1) {
int sum = tb.ask(1,Y[i].first);
V += sum;
E += sum - 1;
}
if(Y[i].second <= n) {
int sum = tb.ask(Y[i].second,n);
V += sum;
E += sum - 1;
}
}
else {
int t = tb.ask(1,n);
V += t;
E += t - 1;
}
for(int x : del[i]) tb.add(x,-1);
// cout << V << " " << E << endl;
}
}
int lshx[SZ],lshy[SZ];
pii a[SZ];
char opt[SZ];
int main() {
int T = read();
while(T --) {
int n = read(),m = read(),k = read();
lshx[0] = lshy[0] = 0;
for(int i = 1;i <= k;i ++) {
a[i].first = read();
a[i].second = read();
char s[2]; scanf("%s",s);
opt[i] = s[0];
lshx[++ lshx[0]] = a[i].first;
lshy[++ lshy[0]] = a[i].second;
}
lshx[++ lshx[0]] = 0; lshx[++ lshx[0]] = n;
lshy[++ lshy[0]] = 0; lshy[++ lshy[0]] = m;
sort(lshx+1,lshx+1+lshx[0]); n = unique(lshx+1,lshx+1+lshx[0]) - lshx - 1;
sort(lshy+1,lshy+1+lshy[0]); m = unique(lshy+1,lshy+1+lshy[0]) - lshy - 1;
for(int i = 1;i <= k;i ++) {
a[i].first = lower_bound(lshx+1,lshx+1+n,a[i].first)-lshx;
a[i].second = lower_bound(lshy+1,lshy+1+m,a[i].second)-lshy;
// cout << a[i].first << " "<< a[i].second << endl;
}
for(int i = 1;i <= n;i ++) X[i].first = -1,X[i].second = m+1;
for(int i = 1;i <= m;i ++) Y[i].first = -1,Y[i].second = n+1;
for(int i = 1;i <= k;i ++) {
int x = a[i].first,y = a[i].second;
char s[2]; s[0] = opt[i];
if(s[0] == 'U') X[x].second = min(X[x].second,y);
if(s[0] == 'D') X[x].first = max(X[x].first,y);
if(s[0] == 'L') Y[y].first = max(Y[y].first,x);
if(s[0] == 'R') Y[y].second = min(Y[y].second,x);
}
X[1].first = m; X[1].second = 1; X[n].first = m; X[n].second = 1;
Y[1].first = n; Y[1].second = 1; Y[m].first = n; Y[m].second = 1;
// for(int i = 1;i < n;i ++) printf("%d %d\n",X[i].first,X[i].second);
// for(int i = 1;i < m;i ++) printf("%d %d\n",Y[i].first,Y[i].second);
LL V = 0,E = 0;
work(X,Y,n,m,V,E);
work(Y,X,m,n,V,E);
V /= 2;
printf("%lld\n",1+E-V);
}
}
C
题意:给 n 个数,定义 \(f(x)\) 为数字 4 在 x 的十进制表示中的出现次数。求 \(\sum_{S} f(\sum_{i \in S}a_i)\) 。 \(n \le 40,a_i \le 44444444\)
key:思路
考虑把前一半和后一半的组合情况统计好了,现在问题变成给两个数组,问两边各取一个数字求和的 f 之和。
最大就 10 位,所以考虑按位统计。假设当前考虑第 k 位,那么如果两个数组中的数字按照 mod 10^k 排序,那么 a 数组中的每个元素对应了 b 数组中的两个区间(即相加为 4.... 和 14.....)。
直接做是 \(O(10*2^{n/2}n)\) ,注意到如果 k 从低向高枚举,那么其实是计数排序的过程。如果 a 和 b 都有序,那么 a 中随着 i 的增大,两个区间在 b 中只会向前,可以用双指针来扫。所以复杂度是 \(O(10*2^{n/2})\)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
typedef pair<LL,LL> pll;
const int SZ = 2e6 + 10;
const int INF = 1e9 + 10;
const int mod = 1e9 + 7;
const LD eps = 1e-8;
LL read() {
LL n = 0;
char a = getchar();
bool flag = 0;
while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
if(flag) n = -n;
return n;
}
int w[44];
int work(int l,int r,LL a[]) {
int n = r-l+1,tot = 0;
for(int S = 0;S < (1<<n);S ++) {
LL sum = 0;
for(int i = 0;i < n;i ++) {
if(S >> i & 1) sum += w[i+l];
}
a[++ tot] = sum;
}
return tot;
}
int g[10][SZ];
void my_sort(LL a[],int n,int k) {
for(int i = 0;i < 10;i ++) g[i][0] = 0;
LL B = 1;
for(int i = 1;i < k;i ++) B *= 10;
for(int i = 1;i <= n;i ++) {
int x = a[i] / B % 10;
g[x][++ g[x][0]] = a[i];
}
int tot = 0;
for(int i = 0;i < 10;i ++) {
for(int j = 1;j <= g[i][0];j ++) {
a[++ tot] = g[i][j];
}
}
}
LL a[SZ],b[SZ],c[SZ];
int main() {
//freopen("test.in","r",stdin); freopen("my.out","w",stdout);
int T = read();
while(T --) {
int K = read();
for(int i = 1;i <= K;i ++) w[i] = read();
int n = work(1,K/2,a),m = work(K/2+1,K,b);
LL B = 1,ans = 0;
for(int k = 1;k <= 10;k ++,B *= 10) {
my_sort(a,n,k); my_sort(b,m,k);
// for(int i = 1;i <= n;i ++) printf("%lld ",a[i]); puts("");
// for(int i = 1;i <= m;i ++) printf("%lld ",b[i]); puts("");
for(int i = 1;i <= m;i ++) c[i] = b[i] % (B*10);
int l1 = m+1,r1 = m;
int l2 = m+1,r2 = m;
for(int i = 1;i <= n;i ++) {
int A = a[i] % (B*10);
while(l1 >= 2 && A + c[l1-1] >= 4 * B) l1 --;
while(r1 >= 1 && A + c[r1] >= 5 * B) r1 --;
while(l2 >= 2 && A + c[l2-1] >= 14 * B) l2 --;
while(r2 >= 1 && A + c[r2] >= 15 * B) r2 --;
int t = r1 - l1 + 1 + r2 - l2 + 1;
if(t) {
// printf("%d : [%d,%d] [%d,%d]\n",i,l1,r1,l2,r2);
ans += t;
}
}
}
printf("%lld\n",ans);
}
}
D
题意:求序列 \(1,2,...,n\) 的等比子序列个数。 \(n \le 5*10^{17}\)
key:推公式
长度小于等于 2 的直接算,现在看大于 3 的。
假设公比是 \(a/b\) ,满足 a>b 且 gcd(a,b)=1,假设长度为 k ,第一项是 A ,那么最后一项是 \(A\frac{a}{b}^{k-1}\) 。由于这个是整数,那么一定要满足 \(b^{k-1}|A\) 。所以 \(a^{k-1}\) 必是其因子。由于 b<a ,所以确定 a,b,k 和最后一项之后,该等比子序列也随之确定,并且是唯一的。
所以等比数列个数是
\[ \sum_{a=2}^n \phi(a)\lfloor \frac{n}{a^{k-1}} \rfloor \]
k>3时直接暴力,k=3 时可以证明 \(\lfloor \frac{n}{a^2} \rfloor\) 只有三次根号个,所以就数论分块+杜教筛。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
const int SZ = 5e7 + 10;
const int INF = 1e9 + 10;
const int mod = 998244353;
const LD eps = 1e-8;
LL read() {
LL n = 0;
char a = getchar();
bool flag = 0;
while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
if(flag) n = -n;
return n;
}
const int MAXN = 5e7;
bool vis[SZ];
int pri[SZ],tot,phi[SZ],sum_phi[SZ];
void shai(int n) {
phi[1] = 1;
for(int i = 2;i <= n;i ++) {
if(!vis[i]) pri[++ tot] = i,phi[i] = i - 1;
for(int j = 1,m;j <= tot && (m=i*pri[j]) <= n;j ++) {
vis[m] = 1;
if(i%pri[j] == 0) {
phi[m] = phi[i] * pri[j];
break;
}
else {
phi[m] = phi[i] * (pri[j] - 1);
}
}
}
for(int i = 1;i <= n;i ++) sum_phi[i] = (sum_phi[i-1] + phi[i]) % mod;
}
LL f1(LL n) {
n %= mod;
return n * (n + 1) % mod * ((mod+1)/2) % mod;
}
unordered_map<LL,LL> mp;
LL dfs(LL n) {
if(n <= MAXN) return sum_phi[n];
if(mp.count(n)) return mp[n];
LL ans = f1(n);
for(LL i = 2,r;i <= n;i = r + 1) {
r = n / (n / i);
(ans -= dfs(n / i) * (r-i+1) % mod) %= mod;
}
ans += mod; ans %= mod;
return ans;
}
LL work3(LL n) {
LL ans = 0;
for(LL a = 2,r;a * a <= n;a = r + 1) {
r = sqrt(n/(n/a/a));
(ans += (dfs(r) - dfs(a-1)) * ((n/a/a) % mod) % mod) %= mod;
}
ans += mod; ans %= mod;
return ans;
}
LL work0(LL n) {
LL ans = f1(n);
for(LL a = 2;;a ++) {
LL now = a * a;
if(now > n / a) break;
now *= a;
while(1) {
(ans += phi[a] * ((n / now)%mod) % mod) %= mod;
if(now > n / a) break;
now *= a;
}
}
return ans;
}
int main() {
shai(MAXN);
int T = read();
while(T --) {
LL n = read();
LL ans3 = work3(n);
LL ans0 = work0(n);
LL ans = (ans3 + ans0) % mod;
printf("%lld\n",ans);
// printf("0:%lld 3:%lld\n",ans0,ans3);
}
}
G
题意:给一棵树,定义(a,b) 合法当且仅当树中存在一条长为 a 的路径和一条长为 b 的路径,且两路径不相交。求合法点对的个数。 \(n \le 10^5\)
key:树的直径,st表
题解做法:直径的两个端点一定都被使用了。对于每个 a 找到最大的 b ,分情况讨论一下,能搞到 O(n)
我的做法:实际上所有的选法一定是【小于等于子树内直径的所有数】和【小于等于子树外直径的所有数】的笛卡尔积。对每个 a 找最大的 b,实际上是一个序列前缀取 max 的操作,操作完后询问每个点的答案,可以用 st 表实现。而这两个东西可以用两遍树形dp实现。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
typedef pair<LL,LL> pll;
const int SZ = 1e6 + 10;
const int INF = 1e9 + 10;
const int mod = 1e9 + 7;
const LD eps = 1e-8;
LL read() {
LL n = 0;
char a = getchar();
bool flag = 0;
while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
if(flag) n = -n;
return n;
}
/*
void kaizhan() {
int size = 256 << 20; // 256MB
char *p = (char*)malloc(size) + size;
__asm__("movl %0, %%esp\n" :: "r"(p));
}
*/
int st[SZ][22];
vector<int> G[SZ];
int f[SZ],g[SZ];
void dfs1(int u,int fa) {
f[u] = 1; g[u] = 1;
for(int v : G[u]) {
if(v == fa) continue;
dfs1(v,u);
g[u] = max(g[u],f[u] + f[v]);
f[u] = max(f[u],f[v] + 1);
}
}
void change_max(int l,int r,int d) {
if(r<0||d<0) return ;
int k = log2(r-l+1);
st[l][k] = max(st[l][k],d);
st[r-(1<<k)+1][k] = max(st[r-(1<<k)+1][k],d);
}
struct haha {
set<pii> s;
void push(int x,int y) {
s.insert(make_pair(x,y));
}
void del(int x,int y) {
s.erase(make_pair(x,y));
}
int find_one_max() {
if(s.empty()) return -INF;
return prev(s.end()) -> first;
}
int find_two_max() {
if(s.size() < 2) return -INF;
return prev(s.end()) -> first + prev(prev(s.end())) -> first;
}
};
void dfs2(int u,int fa,int w0,int w1) {
change_max(1,g[u],w1);
change_max(1,w1,g[u]);
//printf("%d %d %d\n",u,w0,w1);
haha s,s2;
for(int v : G[u]) {
if(v == fa) continue;
s.push(f[v],v);
s2.push(g[v],v);
}
for(int v : G[u]) {
if(v == fa) continue;
s.del(f[v],v);
s2.del(g[v],v);
int ww1 = max(max(max(w1,s.find_two_max()+1),
max(max(s.find_one_max()+1,w0),s.find_one_max()+w0)),
s2.find_one_max());
int ww0 = max(w0+1,s.find_one_max()+2);
dfs2(v,u,ww0,ww1);
s.push(f[v],v);
s2.push(g[v],v);
}
}
int main() {
// kaizhan(); freopen("test.in","r",stdin); freopen("my.out","w",stdout);
int T = read();
while(T --) {
int n = read();
for(int i = 1;i <= n;i ++) G[i].clear();
for(int i = 1;i < n;i ++) {
int x = read();
int y = read();
G[x].push_back(y);
G[y].push_back(x);
}
if(n == 1) { puts("0"); continue; }
if(n == 2) { puts("1"); continue; }
for(int j = 0;j <= log2(n);j ++) {
for(int i = 1;i + (1<<j) - 1 <= n;i ++) {
st[i][j] = 0;
}
}
int rt;
for(int i = 1;i <= n;i ++) {
if(G[i].size() != 1) {
rt = i;
break;
}
}
// printf("%d\n",rt);
dfs1(rt,0);
//for(int i = 1;i <= n;i ++) printf("%d ",f[i]); puts("");
dfs2(rt,0,-INF,-INF);
for(int j = log2(n);j >= 1;j --) {
for(int i = 1;i + (1<<j) - 1 <= n;i ++) {
st[i][j-1] = max(st[i][j-1],st[i][j]);
st[i+(1<<(j-1))][j-1] = max(st[i+(1<<(j-1))][j-1],st[i][j]);
}
}
LL ans = 0;
for(int i = 1;i <= n;i ++) ans += st[i][0];
// for(int i = 1;i <= n;i ++) printf("%d ",st[i][0]); puts("");
printf("%lld\n",ans);
}
}
/**
233
10
1 2
2 3
3 4
2 5
5 6
6 7
1 8
8 9
9 10
*/
H
题意:给 n 个数 \(a_i\) 和 n 个数 \(b_i\) ,要使两两配对,权值是 \(a_i \ xor \ b_j\) ,求权值和最大是多少。 \(n \le 10^5\)
key:trie,套路
跟今年多校第五场的 B 一模一样,用那个套路的话只需要把找异或最小值改为找最大值即可。
传送门:https://www.cnblogs.com/dqsssss/p/11312849.html
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
const int SZ = 1e5 + 10;
const int INF = 1e9 + 10;
const int mod = 1e9 + 7;
const LD eps = 1e-8;
LL read() {
LL n = 0;
char a = getchar();
bool flag = 0;
while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
if(flag) n = -n;
return n;
}
const int MAXN = 2 * 31 * SZ;
struct Trie {
int ch[MAXN][2],sz[MAXN],val[MAXN];
int Tcnt = 0;
void insert(pii v) {
int p = 0;
for(int i = 29;i >= 0;i --) {
int c = v.first >> i & 1;
sz[p] ++;
if(!ch[p][c]) ch[p][c] = ++ Tcnt;
assert(Tcnt < 310 * SZ);
p = ch[p][c];
}
sz[p] ++;
val[p] = v.second;
}
pii find_xor_max(int x) {
int p = 0,ans = 0;
for(int i = 29;i >= 0;i --) {
int c = x >> i & 1;
if(ch[p][c^1] && sz[ch[p][c^1]]) p = ch[p][c^1],ans |= (c^1)<<i;
else p = ch[p][c],ans |= c<<i;
}
assert(sz[p]);
return make_pair(ans,val[p]);
}
void erase(int x) {
int p = 0;
for(int i = 29;i >= 0;i --) {
int c = x >> i & 1;
sz[p] --;
p = ch[p][c];
}
sz[p] --;
val[p] = 0;
}
void clear() {
for(int i = 0;i <= Tcnt;i ++) {
sz[i] = 0;
val[i] = 0;
memset(ch[i],0,sizeof ch[i]);
}
Tcnt = 0;
}
}tree[2];
struct haha {
int v,t,type,id;
};
mt19937 rd(time(0));
int randlr(int l,int r) { return rd()%(r-l+1)+l; }
int main() {
int T = read();
while(T --) {
tree[0].clear();
tree[1].clear();
int n;
n = read();
vector<haha> V;
for(int o = 0;o < 2;o ++) {
map<int,int> mp;
for(int i = 1;i <= n;i ++) {
int x = read();
mp[x] ++;
}
for(pii p : mp) {
V.push_back((haha){p.first,p.second,o,V.size()});
}
}
for(int i = 0;i < V.size();i ++) {
tree[V[i].type].insert(make_pair(V[i].v,i));
}
vector<int> ans,ins; ins.resize(V.size());
for(int i = 0;i < V.size();i ++) {
if(V[i].t == 0) continue;
stack<int> S;
S.push(i); ins[i] = 1;
while(S.size()) {
haha u = V[S.top()]; S.pop(); ins[u.id] = 0;
pii p = tree[u.type^1].find_xor_max(u.v);
assert(V[p.second].type != u.type);
if(!ins[p.second]) {
S.push(u.id); S.push(p.second);
ins[u.id] = ins[p.second] = 1;
}
else {
haha v = V[p.second];
ins[v.id] = 0; S.pop();
tree[u.type].erase(u.v);
tree[v.type].erase(v.v);
while(V[u.id].t && V[v.id].t) {
ans.push_back(u.v^v.v);
V[u.id].t --; V[v.id].t --;
}
if(V[u.id].t) {
tree[u.type].insert(make_pair(u.v,u.id));
}
if(V[v.id].t) {
S.push(v.id);
ins[v.id] = 1;
tree[v.type].insert(make_pair(v.v,v.id));
}
}
}
}
LL sum = 0;
for(int i = 0;i < ans.size();i ++) {
sum += ans[i];
}
printf("%lld\n",sum);
}
return 0;
}