2020 CCPC-Wannafly Winter Camp Day3
A. 黑色气球
数据保证答案唯一,则随便搞搞即可。
Code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MAXN = 1e3+5,MAXM = 1e6+5,MOD = 998244353,INF = 0x3f3f3f3f,N=2e5;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-7;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define pii pair<int,int>
#define vii vector<pii>
#define vi vector<int>
#define x first
#define y second
using namespace std;
int n,h[MAXN][MAXN];
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>h[i][j];
}
}
int a = (h[1][2] + h[1][3] - h[2][3]) / 2;
cout <<a<<' ';
for(int i=2;i<=n;i++){
cout << h[1][i]-a<<" \n"[i==n];
}
return 0;
}
B. 小吨的点分治
题意大致为现在点分治中每次任意在该连通块中选择一个作为分治中心,然后类似于点分治那样不断递归。现在询问有多少种分治方案。
下面是一份暴力代码用于理解:
暴力
const int mod=1e9+7;
const int maxn=5005;
bool vis[maxn];
vector<int> e[maxn];
int n;
inline void view_all(vector<int> &cur,int x,int fa){
cur.push_back(x);
for(int p: e[x]){
if (vis[p]) continue;
if (p == fa) continue;
view_all(cur, p, x);
}
}
inline int calc(int x){
vector<int> cur;
int ans = 0;
view_all(cur, x, -1);
for(int w: cur){
int res = 1;
vis[w] = 1;
for(int p: e[w]){
if (vis[p]) continue;
res = 1ll * res * calc(p) % mod;
}
vis[w] = 0;
ans = (ans + res) % mod;
}
return ans;
}
inline int get_ans(){
return calc(1);
}
这个题的思路感觉不太好说清楚,接下来就说下我的理解。
首先假设我们已经得到了一颗点分树,那么对于原树中的任意一条边\((u,v)\),我们去掉这条边然后将两个连通块染为黑白两色,那么在点分树中将对应颜色染出来。然后有个这样的结论:
- 所有的黑色结点和白色结点都在点分树的不同子树中,其中一颗子树为整棵树。
那么考虑现在有两颗点分树,在原树中我们要添加\((u,v)\)这条边,在点分树中怎么合并呢。
感受一下,会发现只需要按任意顺序归并点分树中\(u\)到根路径上的结点和\(v\)到根路径上的结点即可。
如果不好理解的话,可以结合上面的结论以及选择分治中心的任意性理解一下。
那么我们只需要关注每个点在点分树中的深度即可,之后就考虑\(dp:dp[x][i]\)表示\(x\)点深度为\(i\)时方案数,\(dp\)转移式如下:
含义即为我们枚举\(x\)在合并之后的树中的深度\(i\),那么他在原数中的深度为\(j\),二者的深度差为\(i-j\),说明有\(i-j\)个结点由第二颗子树提供。所以\(y\)的深度必须在\([i-j,n]\)的范围内。最后要选出\(i-j\)个位置插入。
转移的时候第二个求和式可以用后缀和优化,然后我们转移的时候添加限制,每一对结点只会在其\(lca\)的位置处会被用到。最终时间复杂度就为\(O(n^2)\)。
这是什么神仙题。。
Code
/*
* Author: heyuhhh
* Created Time: 2020/2/10 20:45:41
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 5000 + 5, MOD = 1e9 + 7;
int fac[N], inv[N];
int qpow(ll a, ll b) {
ll res = 1;
while(b) {
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int C(int n, int m) {
return 1ll * fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
void add(int &x, int y) {
x += y;
if(x >= MOD) x -= MOD;
}
vector <int> G[N];
int n;
int sz[N], tmp[N], f[N][N];
void dp(int u, int fa) {
sz[u] = 1;
f[u][1] = 1;
for(auto v : G[u]) if(v != fa) {
dp(v, u);
sz[u] += sz[v];
memset(tmp, 0, sizeof(tmp));
for(int i = 1; i <= sz[u]; i++) {
for(int j = max(1, i - sz[v]); j <= min(i, sz[u] - sz[v]); j++) {
add(tmp[i], 1ll * f[v][i - j] * f[u][j] % MOD * C(i - 1, i - j) % MOD);
}
}
for(int i = 1; i <= sz[u]; i++) f[u][i] = tmp[i];
}
for(int i = sz[u]; i >= 0; i--) {
add(f[u][i], f[u][i + 1]);
}
}
void run(){
for(int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
fac[0] = 1;
for(int i = 1; i < N; i++) fac[i] = 1ll * fac[i - 1] * i % MOD;
inv[N - 1] = qpow(fac[N - 1], MOD - 2);
for(int i = N - 2; i >= 0; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;
dp(1, 0);
int ans = f[1][1];
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
while(cin >> n) run();
return 0;
}
C. 无向图定向
最终要确定出一个\(DAG\),假设已经得到了这样一个\(DAG\),然后我们用拓扑序将其分层,易知最长路就等于最大层数-1。
我们现在就要使得层数尽可能少,容易发现,结点在同一层中的充要条件为两两没有边相连。
所以问题就转化为了将无向图划分为个数最少的独立集,直接状压\(dp\)即可。
Code
/*
* Author: heyuhhh
* Created Time: 2020/2/9 17:58:39
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 18;
int n, m;
int f[1 << N], ok[1 << N];
int a[N][N];
void run(){
for(int i = 1; i <= m; i++) {
int u, v; cin >> u >> v;
a[u][v] = a[v][u] = 1;
}
for(int i = 0; i < 1 << n; i++) {
ok[i] = 1;
for(int j = 1; j <= n; j++) {
if(i >> (j - 1) & 1) {
for(int k = j + 1; k <= n; k++) {
if(i >> (k - 1) & 1) {
if(a[j][k]) ok[i] = 0;
}
}
}
}
}
memset(f, INF, sizeof(f));
f[0] = 0;
for(int i = 0; i < 1 << n; i++) {
for(int j = i; j > 0; j = (j - 1) & i) {
if(ok[j]) f[i] = min(f[i], f[i ^ j] + 1);
}
}
cout << f[(1 << n) - 1] - 1 << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
while(cin >> n >> m) run();
return 0;
}
D. 求和
接下来是推式子时间:
故
则
所以杜教筛预处理\(\varphi\)的前缀和,然后整除分块即可。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e6 + 5;
int mu[N], p[N];
ll phi[N];
bool chk[N];
unordered_map <int, ll> mp1, mp2;
int MOD, inv6;
ll qpow(ll a, ll b) {
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return ans;
}
void init() {
inv6 = qpow(6, MOD - 2);
phi[1] = 1;
int cnt = 0, k = N - 1;
for(int i = 2; i <= k; i++) {
if(!chk[i]) p[++cnt] = i, mu[i] = -1, 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] % MOD; break;}
phi[i * p[j]] = phi[i] * (p[j] - 1) % MOD;
}
}
for(int i = 1; i < N; i++) {
phi[i] = (phi[i - 1] + phi[i]) % MOD;
}
}
ll djs_phi(int n) {
if(n <= 5000000) return phi[n];
if(mp2[n]) return mp2[n];
ll ans = 1ll * (n + 1) * n / 2 % MOD;
for(int i = 2, j; i <= n; i = j + 1) {
j = n / (n / i);
ans -= 1ll * (j - i + 1) * djs_phi(n / i) % MOD;
if(ans < 0) ans += MOD;
}
return mp2[n] = ans;
}
int n;
int S(int n) {
return 1ll * n * (n + 1) % MOD * (2 * n + 1) % MOD * inv6 % MOD;
}
int main() {
cin >> n >> MOD;
init();
int ans = 0;
for(int i = 1, j; i <= n; i = j + 1) {
j = n / (n / i);
int up = n / i;
int res = S(up);
ans = (ans + 1ll * res * (djs_phi(j) - djs_phi(i - 1) + MOD) % MOD) % MOD;
}
cout << ans << '\n';
return 0;
}
E. 棋技哥
存在一个结论,只要左上角为黑,则火山哥必胜。
因为火山哥赢的充要条件为游戏最终进行奇数轮就结束,那么必然\((1,1)\)初始为黑才行。
Code
#include<bits/stdc++.h>
using namespace std;
char s[2333];
int main() {
int t; scanf("%d", &t);
while(0<t--) {
bool ac=false;
int n,m; scanf("%d%d", &n, &m);
for(int i=0; i<n; i++) {
scanf("%s", s);
if(i==0 && s[0]=='1') ac=true;
}
if(ac) puts("call");
else puts("aoligei");
}
return 0;
}
F. 社团管理
决策单调性\(dp\),还不会,填坑。
Code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MAXN = 1e5+5,MAXM = 1e6+5,MOD = 998244353,INF = 0x3f3f3f3f,N=2e5;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-7;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define pii pair<int,int>
#define vii vector<pii>
#define vi vector<int>
#define x first
#define y second
using namespace std;
int n,k,a[MAXN],c[MAXN];
ll f[MAXN],g[MAXN];
void solve(int l,int r,int L,int R,ll w){
if(l>r)return ;
int m = (l+r)>>1,k=0,p=m<R?m:R;
for(int i=l;i<=m;i++)w+=c[a[i]]++;
for(int i=L;i<=p;i++){
w-=--c[a[i]];
if(l== n&& r==n){
//cout <<n<<' '<<i<<' '<<f[i]<<' '<<w<<'\n';
}
if(g[m] > f[i] + w){
g[m] = f[i] + w;
k=i;
}
}
for(int i=L;i<=p;i++){
w += c[a[i]]++;
}
for(int i=l;i<=m;i++)w-=--c[a[i]];
solve(l,m-1,L,k,w);
for(int i=l;i<=m;i++)w+=c[a[i]]++;
for(int i=L;i<k;i++)w-=--c[a[i]];
solve(m+1,r,k,R,w);
for(int i=L;i<k;i++)++c[a[i]];
for(int i=l;i<=m;i++)--c[a[i]];
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
f[i] = f[i-1] + c[a[i]]++;
}
memset(c,0,sizeof(c));
while(--k){
memset(g,0x3f,sizeof(g));
solve(1,n,1,n,0);
swap(f,g);
}
cout<<f[n]<<'\n';
return 0;
}
G. 火山哥周游世界
容易分析,最终火山哥的路线为一条欧拉序减去一条最长链
那么直接换根\(dp\)处理出离所有结点长度最长的链。
之后再换根\(dp\)求出每个结点作为根时,到达所有关键点的边权和乘以\(2\)的值。二者相减即为每个结点作为根时的情况。
代码实现起来较为复杂,细节见代码。主要是维护最长链时要维护最长和次长链,这样就比较方便转移。
Code
/*
* Author: heyuhhh
* Created Time: 2020/1/14 14:23:18
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 5e5 + 5;
int n, k;
struct Edge {
int v, w, next;
}e[N << 1];
int head[N], tot;
void adde(int u, int v, int w) {
e[tot].v = v; e[tot].w = w; e[tot].next = head[u]; head[u] = tot++;
}
bool chk[N];
int sz[N];
ll f[N][2], g[N][2], L[N];
ll ans[N];
void dfs(int u, int fa) {
sz[u] = (chk[u] == true);
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v, w = e[i].w;
if(v == fa) continue;
dfs(v, u);
sz[u] += sz[v];
}
}
void dfs2(int u, int fa) {
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v, w = e[i].w;
if(v == fa) continue;
dfs2(v, u);
if(sz[v]) {
ans[u] += ans[v] + w;
if(f[v][0] + w > f[u][0]) {
f[u][1] = f[u][0];
g[u][1] = g[u][0];
f[u][0] = f[v][0] + w;
g[u][0] = v;
} else if(f[v][0] + w > f[u][1]) {
f[u][1] = f[v][0] + w;
g[u][1] = v;
}
}
}
}
void dfs3(int u, int fa, ll h) {
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v, w = e[i].w;
if(v == fa) continue;
ll mx;
if(g[u][0] == v) mx = max(h, f[u][1]);
else mx = max(h, f[u][0]);
L[v] = max(f[v][0], mx + w);
dfs3(v, u, mx + w);
}
}
void dfs4(int u, int fa) {
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v, w = e[i].w;
if(v == fa) continue;
ans[v] = ans[u] - 2ll * (sz[v] > 0) * w + 2ll * (k - sz[v] > 0) * w;
dfs4(v, u);
}
}
void run(){
memset(head, -1, sizeof(head)); tot = 0;
for(int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
adde(u, v, w); adde(v, u, w);
}
for(int i = 1; i <= k; i++) {
int x; cin >> x;
chk[x] = 1;
}
dfs(1, 0);
dfs2(1, 0);
L[1] = f[1][0];
dfs3(1, 0, 0);
ans[1] <<= 1;
dfs4(1, 0);
for(int i = 1; i <= n; i++) ans[i] -= L[i];
for(int i = 1; i <= n; i++) cout << ans[i] << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
while(cin >> n >> k) run();
return 0;
}
H. 火山哥的序列
考虑如果直接枚举删除区间的左右端点,时间复杂度为\(O(n^2logn)\),显然不能承受。
注意到值域范围为\([1,2\cdot 10^5]\),所以我们考虑直接枚举最终答案\(x\),找到其倍数,若位置为\(p_1,p_2,\cdots,p_{t-1},p_t\),那么区间的划分就有三种情况:
- 左端点\(l>p_2\),此时留出最左边两个;
- 左端点\(p_1<l\leq p_2\),右端点\(r<p_t\),此时留出最左边和最右边的一个;
- 左端点\(l\leq p_1\),右端点\(r<p_{t-1}\),此时留出最右边两个。
考虑以每个点作为左端点,维护当前已经划分到了最远的右端点,那么每次对于一个\(x\)来说,只有增加的部分有用。
因为我们要取最大的\(gcd\),所以直接倒序枚举答案,每次只用考虑右端点的增量即可(如果是正序枚举,因为大的会覆盖小的,不好处理)。
细节详见代码:
Code
/*
* Author: heyuhhh
* Created Time: 2020/2/10 11:02:03
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 2e5 + 5;
int n;
int a[N], p[N];
int maxv[N << 2], minv[N << 2], lz[N << 2];
ll sumv[N << 2];
void tag(int o, int l, int r, int v) {
lz[o] = minv[o] = maxv[o] = v;
sumv[o] = 1ll * (r - l + 1) * v;
}
void push_up(int o) {
maxv[o] = max(maxv[o << 1], maxv[o << 1|1]);
minv[o] = min(minv[o << 1], minv[o << 1|1]);
sumv[o] = sumv[o << 1] + sumv[o << 1|1];
}
void push_down(int o, int l, int r) {
if(lz[o] != -1) {
int mid = (l + r) >> 1;
tag(o << 1, l, mid, lz[o]);
tag(o << 1|1, mid + 1, r, lz[o]);
lz[o] = -1;
}
}
void build(int o, int l, int r) {
lz[o] = -1;
if(l == r) {
minv[o] = maxv[o] = l - 1;
sumv[o] = l - 1;
return;
}
int mid = (l + r) >> 1;
build(o << 1, l, mid);
build(o << 1|1, mid + 1, r);
push_up(o);
}
void update(int o, int l, int r, int L, int R, int v) {
if(L <= l && r <= R) {
if(minv[o] >= v) return;
if(maxv[o] < v) {
tag(o, l, r, v);
return;
}
}
push_down(o, l, r);
int mid = (l + r) >> 1;
if(L <= mid) update(o << 1, l, mid, L, R, v);
if(R > mid) update(o << 1|1, mid + 1, r, L, R, v);
push_up(o);
}
void run(){
memset(p, 0, sizeof(p));
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i], p[a[i]] = i;
build(1, 1, n);
ll ans = 0;
for(int i = N - 1; i >= 1; i--) {
int a = N, b = N, c = -N, d = -N;
for(int j = i; j < N; j += i) if(p[j]) {
if(a > p[j]) {
b = a, a = p[j];
} else if(b > p[j]) b = p[j];
if(d < p[j]) {
c = d, d = p[j];
} else if(c < p[j]) c = p[j];
}
if(b == N) continue;
ll last = sumv[1];
if(b < n) update(1, 1, n, b + 1, n, n);
update(1, 1, n, a + 1, b, d - 1);
update(1, 1, n, 1, a, c - 1);
ans += (sumv[1] - last) * i;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。