2018 - 2019 SEERC 題解
比賽發出來太新了,網上根本就搜不到題解,補題補的太難受了.
在這裏分享一篇我自己寫的題解,也方便別人補題.
題目鏈接
http://codeforces.com/gym/101964/attachments/download/7814/seerc-2018.pdf
A.Numbers
不留坑,這題不會.
B.Broken Watch
題解
先考慮三個針長度各不一樣的情況.
注意到需要對分奇偶性進行討論.
- 爲偶數的時候,固定號針的位置,枚舉號針的位置,那麼號針的位置必然在號針的反向延長線形成的扇形區域內(可與邊界重合).
並注意到當號針反向的時候,號針有種取法.
可以得到公式. - 爲奇數的時候,固定號針的位置枚舉號針的位置,那麼號針的位置必然在號針反向延長線之間(不能與邊界重合).
可以得到公式
而當三根針有兩根相同時,需要對答案除以,當三根全都相同時,需要對答案除以,這題就結束了.不明白爲什麼這個題比題過得人少?
代碼
org = input().split(" ")
a = int(org[0])
b = int(org[1])
c = int(org[2])
n = int(org[3])
len = 1
if a == b and b == c:
len = 6
elif a == b or b == c or a == c:
len = 2
mod = 2**64
ans = 0
if n % 2 == 0:
ans = (n*((n//2-1)*(n//2+2)+(n-2))//len )% mod
else:
ans = (n*(n//2)*(n//2+1)//len) % mod
print(ans)
C.Tree
題解
訓練的時候想了一種樹dp的做法,不太好調,幸好最後還是A掉了.賽後翻比別人代碼發現還有一種很巧妙地方法,即枚舉樹的直徑.兩種方法我都簡略說一下.
方法一.樹dp
二分最大距離,然後樹來可行性.
定義表示以爲根的子樹,選出來的黑點中距節點距離不會超過,所能選出最多的黑點個數.
並記
那麼轉移就是:
假設是的兒子節點.
最後只要看.
時間複雜度?
方法二.枚舉樹的直徑
先預處理出樹上兩點之間的距離(使用Floyd算法即可).
注意到將黑點取出之後會形成一顆虛樹,並且兩兩之間最長的距離就是
然後我們考慮枚舉這顆虛樹的直徑,假設是,然後再枚舉黑點,黑點進到虛樹中一定不能使直徑邊長.所以就要要求.
時間複雜度
這個方法簡單多了.
注意:不需要建虛樹,說虛樹主要是好描述.
代碼
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define pr(x) std::cout << #x << ':' << x << std::endl
#define rep(i,a,b) for(int i = a;i <= b;++i)
using namespace std;
const int maxn = 105;
int n,m,val[maxn],dp[maxn][maxn];
vector<int> G[maxn];
void dfs(int cur,int pre,int mid){
for(int nx : G[cur]) if(nx != pre) dfs(nx,cur,mid);
if(val[cur] == 1) dp[cur][0] = 1;
for(int i = 1;i <= mid;i++){
int limit = min(mid-1-i,i-1), sum = 0;
if(limit >= 0) for(int nx : G[cur]) if(nx != pre){
sum += dp[nx][limit];
}
dp[cur][i] = max(dp[cur][i],dp[cur][i-1]);
for(int nx : G[cur]){
if(nx == pre) continue;
int tmp = dp[nx][i-1];
if(limit >= 0) tmp += sum - dp[nx][limit];
dp[cur][i] = max(dp[cur][i], val[cur]+tmp);
}
}
}
bool check(int mid){
memset(dp,0,sizeof(dp));
dfs(1,-1,mid);
int f = 0;
for(int i = 1;i <= n;++i)
f |= dp[i][mid] >= m;
return f;
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i = 1;i <= n;i++) cin>>val[i];
for(int i = 0;i < n-1;i++){
int u,v;
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
int l = 0, r = n;
while(r - l > 1){
int mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
if(check(l)) cout<<l<<endl;
else cout<<r<<endl;
return 0;
}
D.Space Station
題解
一道很神奇的題,需要先猜出結論,然後再進行樹上揹包.思路弄清楚了實現起來很簡單.
簡述一下題意:一顆個結點的帶權樹,要求從號點出發,遍歷所有的邊,然後回到號點.途中可以最多使用次技能,技能花費時間爲,功能是在任意兩點中穿越.求最小代價.
我們先來發現一些規律性的東西.
- 注意到一顆子樹如果子樹中包含的技能出發點和技能到達點共個,如果是偶數的時候,那麼這顆子樹根節點的父親邊一定要走兩次(可以畫圖幫助理解一下)
- 而如果是奇數的話,那麼子樹根節點的父親邊只需要走一次就好了.(畫圖幫助理解)
這就很簡單了.
定義表示以爲根的子樹,其子樹中奇點個數爲,從點出發遍歷完的子樹再回到點,所需要的最小代價.
那麼轉移方程就是
代碼
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define pr(x) std::cout << #x << ':' << x << std::endl
#define rep(i,a,b) for(int i = a;i <= b;++i)
typedef std::pair<int,int> pii;
const int N = 1007;
std::vector<pii> edge[N];
int dp[N][N],sz[N],tmp[N];
int n,m,k,T;
void upd(int &x,int y){
if(y < x) x = y;
}
void dfs(int u,int fa) {
sz[u] = 1;
for(pii p : edge[u]) {
int v = p.first,c = p.second;
if(v == fa) continue;
dfs(v,u);
memset(tmp,0x3f,sizeof(tmp));
for(int i = 0;i <= sz[u];++i) {
for(int j = 0;j <= sz[v];++j) {
if(i + j > 2*m) continue;
if(j % 2 != 0) {
upd(tmp[i+j],dp[u][i] + dp[v][j] + c);
upd(tmp[i+j+1],dp[u][i] + dp[v][j] + c);
}
else {
upd(tmp[i+j],dp[u][i] + dp[v][j] + 2*c);
upd(tmp[i+j+1],dp[u][i] + dp[v][j] + 2*c);
}
}
}
sz[u] += sz[v];
for(int i = 0;i <= sz[u];++i)
dp[u][i] = tmp[i];
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cin >> T;
while(T--) {
std::cin >> n >> m >> k;
memset(dp,0,sizeof(dp));
rep(i,1,n)
edge[i].clear();
rep(i,1,n-1) {
int u,v,c;
std::cin >> u >> v >> c;
edge[u].push_back((pii){v,c});
edge[v].push_back((pii){u,c});
}
dfs(1,0);
int ans = 0x3f3f3f3f;
for(int i = 0;i <= std::min(2*m,n);i += 2)
upd(ans,dp[1][i]+(i/2*k));
std::cout << ans << std::endl;
}
return 0;
}
E.Fisherman
題解
首先我們將釣魚的人按照座標排個序,同時也記錄一下他在答案裏的順序以便逆映射回去。
然後考慮每一條魚能被哪些漁夫釣到,也就是考慮貢獻。我們能夠很簡單地知道,一條魚能夠被釣到的地方在軸上是一段連續的區間。當魚的y座標大於所給的線的長度時釣不到,否則記魚線長度大於座標的長度,能夠釣到魚區間即爲。於是lower_bound和upper _bound找一下漁夫裏面所對應的區間,然後區間加單點查詢。區間加單點查詢這個操作在代碼裏是使用了差分數組來維護的,最後算完之後把差分數組還原即可得到答案。
F.Min Max Convert
題解
大致的題意是給你兩個長爲的數列,然後你每次可以選擇一段區間將區間覆蓋成它的最大值或者是覆蓋成它的最小值.要求輸出一個長不超過的方案,將數列變成數列.
很明顯的構造題.
我們首先要找一下規律以發現一些結論.
-
一個區間最多通過兩次操作可以將區間覆蓋爲區間中任意一個數.
當在區間邊界上時,例如,將覆蓋爲的方法是:判斷和的大小關係,如果,那麼就將取最小值,再將取最大值.
當在中間時,可以將區間分成兩段,分別操作. -
對於數列的每個數,在數列中都應該有一個數與它對應.並且這些對應關係不交叉.
比如:
那麼對應關係是這樣的:
好難講,直接看我代碼吧.
代碼
#include <iostream>
#include <algorithm>
#include <cstring>
#define pr(x) std::cout << #x << ':' << x << std::endl
#define rep(i,a,b) for(int i = a;i <= b;++i)
const int N = 100007;
int A[N],B[N],Loc[N];
char C[N<<1];int L[N<<1],R[N<<1];
int tot;
struct E{
int b,e,x;
}es[N];
int etot = 0;
int n;
void Do(char c,int l,int r) {
C[tot] = c;L[tot] = l;R[tot] = r;
++tot;
}
int main() {
scanf("%d",&n);
rep(i,1,n)
scanf("%d",&A[i]);
rep(i,1,n)
scanf("%d",&B[i]);
int pos = 1;
rep(i,1,n) {
while(pos <= n && A[pos] != B[i]) pos ++;
if(pos == n+1) return 0*puts("-1");
Loc[i] = pos;
}
int p = 1;
rep(i,1,n) {
if(Loc[i] != Loc[i+1]) {
es[etot++] = (E){p,i,Loc[i]};
p = i+1;
}
}
for(int i = etot-1;i >= 0;--i) {
if(es[i].x < es[i].b) {
if(A[es[i].x] >= A[es[i].x+1]) {
Do('m',es[i].x+1,es[i].e);
Do('M',es[i].x,es[i].e);
}
if(A[es[i].x] < A[es[i].x+1]) {
Do('M',es[i].x+1,es[i].e);
Do('m',es[i].x,es[i].e);
}
}
}
rep(i,0,etot-1) {
if(es[i].x > es[i].e) {
if(A[es[i].x] <= A[es[i].x-1]) {
Do('M',es[i].b,es[i].x-1);
Do('m',es[i].b,es[i].x);
}
if(A[es[i].x] > A[es[i].x-1]) {
Do('m',es[i].b,es[i].x-1);
Do('M',es[i].b,es[i].x);
}
}
}
rep(i,0,etot-1) {
if(es[i].b <= es[i].x && es[i].x <= es[i].e) {
if(es[i].x > es[i].b && A[es[i].x] >= A[es[i].x-1]) {
Do('m',es[i].b,es[i].x-1);
Do('M',es[i].b,es[i].x);
}
if(es[i].x > es[i].b && A[es[i].x] < A[es[i].x-1]) {
Do('M',es[i].b,es[i].x-1);
Do('m',es[i].b,es[i].x);
}
if(es[i].x < es[i].e && A[es[i].x] >= A[es[i].x+1]) {
Do('m',es[i].x+1,es[i].e);
Do('M',es[i].x,es[i].e);
}
if(es[i].x < es[i].e && A[es[i].x] < A[es[i].x+1]) {
Do('M',es[i].x+1,es[i].e);
Do('m',es[i].x,es[i].e);
}
}
}
std::cout << tot << std::endl;
rep(i,0,tot-1) {
printf("%c %d %d\n",C[i],L[i],R[i]);
}
return 0;
}
G.Matrix Queries
題解
隊友寫的.
結論:我們稱依題目給出的公式計算矩陣的得分時遞歸處理到的矩陣均稱爲的“子矩陣”,記這些矩陣中有個爲不“純色”的矩陣,則矩陣A的得分爲。
證明:
用數學歸納法:當爲的矩陣時顯然結論成立。
假設A爲的矩陣時結論成立,現證A爲矩陣時結論仍成立:
- 純色,則其得分爲1,結論成立;
- 不純色,記其四個子矩陣的不純色的子矩陣個數分別爲、、和,則由假設它們的得分分別爲、、和,A中不純色的子矩陣個數爲,由定義A的得分,結論成立。
綜上結論得證。
考慮到一個矩陣爲純色則這個矩陣的每行需修改相同奇偶次且每列也需修改相同奇偶次,統計有多少合法位置的連續行與列修改的奇偶次數相同,相乘結果即大小矩陣中純色的個數,求反面即可。這些區間形成了線段樹的結構,用線段樹維護即可。
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = (1<<20)+10;
ll seg[2][maxn*4],ans[2][21];
void modify(int id,int o,int l,int r,int deep,int x){
if(l == r){
seg[id][o] ^= 1;
return;
}
int mid = (l + r) >> 1;
if(x <= mid) modify(id,o<<1,l,mid,deep+1,x);
else modify(id,o<<1|1,mid+1,r,deep+1,x);
if(seg[id][o] != -1) ans[id][deep]--;
if(seg[id][o<<1] == seg[id][o<<1|1]) seg[id][o] = seg[id][o<<1];
else seg[id][o] = -1;
if(seg[id][o] != -1) ans[id][deep]++;
}
inline void read(int& x){
char ch = getchar();
x = 0;
for(;ch < '0' || ch > '9';ch = getchar());
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x*10+(ch-'0');
}
inline void write(ll x){
char ch = x%10+'0';
if(x >= 10) write(x/10);
putchar(ch);
}
int main(){
ios::sync_with_stdio(false);
int n,m,sz;
ll sum = 0;
read(sz);read(m);
n = (1 << sz);
for(int i = 0;i < sz;i++){
ans[0][i+1] = ans[1][i+1] = (1 << i);
sum += (1ll << (i * 2));
}
for(int i = 0;i < m;i++){
int id, x;
read(id);read(x);
modify(id,1,1,n,1,x);
ll tmp = 0;
for(int i = 1;i <= sz;i++){
tmp += 1ll*ans[0][i] * ans[1][i];
}
write(4ll * (sum - tmp) + 1);puts("");
}
return 0;
}
H.Modern Djinn
題解
留坑.
I.Inversion
題解
第一步,根據圖恢原來的排列.
在得到原來的排列以後,我們從排列中挑選一些位置組成一個獨立支配集.必然有,只有這樣,集合裏的點之間纔沒有邊相連,並且還要滿足條件即之間的數要麼大於,要麼小於.
並且在排列中不可能存在,否則的話,它也應該存在於集合當中,應爲它與集合中的所有點都無邊相連.同理,不存在,使得.
如果之間的數不會存在介於與之間的數,就從向連邊.
答案就是從入度爲的點,跑到出度爲的點的路徑數之和.
拓撲序dp一下結束.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
int a[maxn], in[maxn], cnt, x[maxn],n,m;
ll dp[maxn];
bool vis[maxn][maxn];
vector<int> G[maxn];
ll dfs(int cur){
if(dp[cur] != -1) return dp[cur];
dp[cur] = 0;
if(G[cur].size() == 0) dp[cur]++;
for(int nx : G[cur]){
dp[cur] += dfs(nx);
}
return dp[cur];
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i = 0;i < m;i++){
int u,v;
cin>>u>>v;
vis[u][v] = vis[v][u] = 1;
if(u < v) swap(u,v);
G[u].push_back(v);
in[v]++;
}
for(int i = 1;i <= n;i++){
for(int j = i+1;j <= n;j++){
if(vis[i][j]) continue;
G[i].push_back(j);
in[j]++;
}
}
queue<int> q;
for(int i = 1;i <= n;i++){
if(in[i] == 0) q.push(i);
}
while(!q.empty()){
int cur = q.front();q.pop();
a[cur] = ++cnt;
for(int nx : G[cur]){
in[nx]--;
if(in[nx] == 0) q.push(nx);
}
}
memset(in,0,sizeof(in));
for(int i = 1;i <= n;i++) G[i].clear();
for(int i = 1;i <= n;i++){
for(int j = i+1;j <= n;j++){
if(a[i] > a[j]) continue;
bool flag = true;
for(int k = i + 1;k < j;k++)
if(a[k] > a[i] && a[k] < a[j]) flag = false;
if(flag){
G[i].push_back(j);
in[j]++;
}
}
}
ll ans = 0;
memset(dp,-1,sizeof(dp));
for(int i = 1;i <= n;i++) if(in[i] == 0) ans += dfs(i);
cout<<ans<<endl;
return 0;
}
J.Rabbit vs Turtle
題解
留坑
K.Points and Rectangles
題解
分治的一道比較裸的題.
像這種能用二維線段樹做的題(空間爆炸)都可以轉換成離線cdq分治去解決.
每個點算作個.
每個矩形按照左邊和右邊拆成兩個,每個包含上下邊界.
我們考慮單獨對點和矩形算貢獻.
-
對矩形算貢獻:
所有在該矩形前加的點都對矩形的左邊有個負的影響,對矩形的右邊有個正的貢獻.
維護一個樹狀數組,位置的值代表目前存在的點值爲的有多少個.
那麼對於每一個分治過程按照的從小到大的過程掃過來,遇見矩形左邊就將貢獻減去,遇見矩形右邊,就將貢獻加上.
需要注意的一點是當多個的相同的情況下,我們按照矩形左邊,點,矩形右邊 順序掃. -
對點算貢獻.
矩形的左邊對點會產生正的貢獻,而矩形的右邊會對點產生負的貢獻.
還是按照的值從小到大的過程掃過來,遇見矩形左邊就將新樹狀數組的位置,將位置.
遇見矩形右邊就將新樹狀數組的位置,將位置.
在遇見點時候,直接統計的值就是包含這個點的矩形的貢獻.
需要注意的一點是當多個的相同的情況下,我們按照矩形左邊,點,矩形右邊 順序掃.
代碼
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define pr(x) std::cout << #x << ':' << x << std::endl
#define rep(i,a,b) for(int i = a;i <= b;++i)
#define int long long
int op[3] = {2,1,3};
const int N = 1e6;
struct event{
int tp,id,x,up,down;
event(int tp=0,int id=0,int x=0,int up=0,int down=0):tp(tp),id(id),x(x),up(up),down(down){}
friend bool operator<(event &e1,event &e2) {
return e1.x == e2.x ? (op[e1.tp] < op[e2.tp]) : (e1.x < e2.x);
}
}es[N<<1];
int tot;
int a[N],ans[N];
int bit1[N],bit2[N];
int ec = 0,q;
event tmp[N<<1];
int lowbit(int x) {
return x & (-x);
}
void add(int bit[],int pos,int x) {
for(;pos < N;pos += lowbit(pos))
bit[pos] += x;
}
int sum(int bit[],int pos) {
int res = 0;
for(;pos;pos -= lowbit(pos)) {
res += bit[pos];
}
return res;
}
void clr(int bit[],int pos) {
if(pos <= 0) return ;
for(;pos < N;pos += lowbit(pos))
bit[pos] = 0;
}
void solve(int l,int r) {
if(l == r)
return ;
int mid = (l + r) >> 1;
solve(l,mid);solve(mid+1,r);
std::vector<int> vec1,vec2;
int s = 0,p = l,q = mid+1;
while(p <= mid && q <= r) {
if(es[p] < es[q]) {
if(es[p].tp == 0) {
add(bit1,es[p].up,1);
vec1.push_back(es[p].up);
}
else if(es[p].tp == 1) {
add(bit2,es[p].down,1);
add(bit2,es[p].up+1,-1);
vec2.push_back(es[p].down);
vec2.push_back(es[p].up+1);
}
else if(es[p].tp == 2) {
add(bit2,es[p].down,-1);
add(bit2,es[p].up+1,1);
vec2.push_back(es[p].down);
vec2.push_back(es[p].up+1);
}
tmp[s++] = es[p++];
}
else {
if(es[q].tp == 0) {
ans[es[q].id] += sum(bit2,es[q].up);
}
else if(es[q].tp == 1) {
ans[es[q].id] -= sum(bit1,es[q].up) - sum(bit1,es[q].down-1);
}
else if(es[q].tp == 2) {
ans[es[q].id] += sum(bit1,es[q].up) - sum(bit1,es[q].down-1);
}
tmp[s++] = es[q++];
}
}
while(p <= mid) tmp[s++] = es[p++];
while(q <= r){
if(es[q].tp == 0) {
ans[es[q].id] += sum(bit2,es[q].up);
}
else if(es[q].tp == 1) {
ans[es[q].id] -= sum(bit1,es[q].up) - sum(bit1,es[q].down-1);
}
else if(es[q].tp == 2)
ans[es[q].id] += sum(bit1,es[q].up) - sum(bit1,es[q].down-1);
tmp[s++] = es[q++];
}
for(int x : vec1) clr(bit1,x);
for(int x : vec2) clr(bit2,x);
rep(i,l,r) {
es[i] = tmp[i-l];
}
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin >> q;
a[ec++] = 0;
rep(i,1,q) {
int tp;
std::cin >> tp;
if(tp == 1) {
int x,y;
std::cin >> x >> y;
x+=2,y+=2;
a[ec++] = x;a[ec++] = y;
es[tot++] = event(0,i,x,y,0);
}
else if(tp == 2) {
int _a,_b,_c,_d;
std::cin >> _a >> _b >> _c >> _d;
_a+=2,_b+=2,_c+=2,_d+=2;
a[ec++] = _a;a[ec++] = _b;a[ec++] = _c;a[ec++] = _d;
es[tot++] = event(1,i,_a,_d,_b);
es[tot++] = event(2,i,_c,_d,_b);
}
}
std::sort(a,a+ec);
ec = std::unique(a,a+ec)-a;
for(int i = 0;i < tot;++i) {
es[i].x = std::lower_bound(a,a+ec,es[i].x)-a;
es[i].up = std::lower_bound(a,a+ec,es[i].up)-a;
es[i].down = std::lower_bound(a,a+ec,es[i].down)-a;
}
solve(0,tot-1);
rep(i,1,q) {
ans[i] += ans[i-1];
std::cout << ans[i] << std::endl;
}
return 0;
}