HNOI2016
最小公倍數 (分塊、並查集)
看到這種不能用數據結構(實際上是可以用K-D Tree的)維護的題目就應該想到分塊然后使用並查集維護連通性和一個連通塊中的\(maxa , maxb\)啊QAQ
為了區分詢問的\(ab\)與邊權的\(ab\),詢問的\(ab\)描述變為\(AB\)
對於所有邊按照\(a\)從小到大排序並\(\sqrt{M}\)分塊。設第\(i\)塊的左右端點為\([l_i,r_i]\),那么在詢問中找出\(A \in [a_{l_i} , a_{r_i})\)的所有詢問,並按照\(B\)從小到大排序,一個個做詢問。
對於當前塊的第\(j\)個詢問,有兩種邊可以對這個詢問造成貢獻:
①前\(i-1\)塊滿足\(b \leq B_j\)的邊。注意到排序之后\(B_j\)是遞增的,所以將前\(i-1\)塊按照\(b\)從小到大排序,並用一個指針維護\(b \leq B_j\)的邊
②第\(i\)塊中滿足\(a \leq A_j , b \leq B_j\)的邊。這些邊總共只有\(\sqrt{M}\)條可以暴力去做。可是注意到:可能存在對於之前的詢問滿足條件,但是對於現在的詢問不滿足條件的邊。但是因為有①的邊的存在,又不能暴力重建並查集,那么我們的並查集必須要支持撤銷。所以使用按秩合並並查集,每一次將所有②操作造成的修改扔進一個棧內,詢問完成之后棧序撤銷,就能保證復雜度。
總復雜度\(O(q\sqrt{M}logN)\)
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<cassert>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
while(!isdigit(c) && c != EOF)
c = getchar();
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return a;
}
const int MAXN = 5e4 + 3;
struct thing{
int s , t , A , B , ind;
}now[MAXN << 1] , que[MAXN];
int fa[MAXN] , sz[MAXN] , maxA[MAXN] , maxB[MAXN] , st[500][3];
int N , M , T , Q , top;
vector < thing > pot , cur , tmp;
bool ans[MAXN];
bool operator <(thing a , thing b){
return a.A < b.A;
}
bool cmp(thing a , thing b){
return a.B < b.B;
}
void init(){
for(int i = 1 ; i <= N ; ++i){
fa[i] = i;
sz[i] = 1;
maxA[i] = maxB[i] = -1;
}
}
inline int find(int a){
while(fa[a] != a) a = fa[a];
return a;
}
void mge(int a , int b , int A , int B , bool f = 0){
a = find(a);
b = find(b);
if(sz[a] < sz[b])
a ^= b ^= a ^= b;
if(f){
st[++top][0] = b;
st[top][1] = maxA[a];
st[top][2] = maxB[a];
}
if(a != b) sz[a] += sz[b];
fa[b] = a;
maxA[a] = max(maxA[a] , max(maxA[b] , A));
maxB[a] = max(maxB[a] , max(maxB[b] , B));
}
void clear(){
while(top){
if(fa[st[top][0]] != st[top][0])
sz[fa[st[top][0]]] -= sz[st[top][0]];
maxA[fa[st[top][0]]] = st[top][1];
maxB[fa[st[top][0]]] = st[top][2];
fa[st[top][0]] = st[top][0];
--top;
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
N = read();
M = read();
T = sqrt(M);
for(int i = 1 ; i <= M ; ++i){
now[i].s = read();
now[i].t = read();
now[i].A = read();
now[i].B = read();
}
Q = read();
for(int i = 1 ; i <= Q ; ++i){
que[i].s = read();
que[i].t = read();
que[i].A = read();
que[i].B = read();
que[i].ind = i;
}
sort(now + 1 , now + M + 1);
sort(que + 1 , que + Q + 1);
int p = 0 , q = 1;
++M;
now[M].s = now[M].t = 1;
now[M].A = now[M].B = 0x7fffffff;
while(p < M){
init();
int p1 = p;
while(p1 < M && p1 - p != T)
++p1;
while(q <= Q && que[q].A < now[p1].A)
cur.push_back(que[q++]);
sort(cur.begin() , cur.end() , cmp);
int pnt = 0;
for(int i = 0 ; i < cur.size() ; ++i){
while(pnt < pot.size() && pot[pnt].B <= cur[i].B){
mge(pot[pnt].s , pot[pnt].t , pot[pnt].A , pot[pnt].B);
++pnt;
}
for(int j = p ; j < p1 && now[j].A <= cur[i].A ; ++j)
if(now[j].B <= cur[i].B)
mge(now[j].s , now[j].t , now[j].A , now[j].B , 1);
int s = find(cur[i].s);
ans[cur[i].ind] = s == find(cur[i].t) && maxA[s] == cur[i].A && maxB[s] == cur[i].B;
clear();
}
sort(now + p + 1 , now + p1 + 1 , cmp);
tmp.clear();
int pPot = 0;
while(pPot < pot.size() || p != p1)
if(p != p1 && (pPot == pot.size() || pot[pPot].B > now[p + 1].B))
tmp.push_back(now[++p]);
else
tmp.push_back(pot[pPot++]);
pot = tmp;
cur.clear();
}
for(int i = 1 ; i <= Q ; ++i)
puts(ans[i] ? "Yes" : "No");
return 0;
}
網絡 (整體二分、樹狀數組)
比較裸的整體二分題目
check中按照時間順序模擬操作,對於比當前二分值大的添加和刪除操作在樹狀數組上維護鏈並,對於一次詢問查詢這個點的子樹的權值和是否不為\(0\)
#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
bool f = 0;
char c = getchar();
while(c != EOF && !isdigit(c)){
if(c == '-')
f = 1;
c = getchar();
}
while(c != EOF && isdigit(c)){
a = (a << 3) + (a << 1) + (c ^ '0');
c = getchar();
}
return f ? -a : a;
}
const int MAXN = 100010;
struct query{
int ind , l , r , LCA , wei , place;
}now[MAXN << 1] , potL[MAXN << 1] , potR[MAXN << 1];
struct Ed{
int end , upEd;
}Ed[MAXN << 1];
int jump[MAXN][20] , head[MAXN] , dep[MAXN] , dfn[MAXN] , ans[MAXN] , size[MAXN] , Tree[MAXN];
int ts , N , M , cntEd , cntQ;
inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
}
void dfs(int now , int fa){
size[now] = 1;
dfn[now] = ++ts;
dep[now] = dep[fa] + 1;
jump[now][0] = fa;
for(int i = 1 ; i <= 19 ; i++)
jump[now][i] = jump[jump[now][i - 1]][i - 1];
for(int i = head[now] ; i ; i = Ed[i].upEd)
if(Ed[i].end != fa){
dfs(Ed[i].end , now);
size[now] += size[Ed[i].end];
}
}
inline int jumpToLCA(int x , int y){
if(dep[x] < dep[y])
swap(x , y);
for(int i = 19 ; i >= 0 ; i--)
if(dep[x] - (1 << i) >= dep[y])
x = jump[x][i];
if(x == y)
return y;
for(int i = 19 ; i >= 0 ; i--)
if(jump[x][i] != jump[y][i]){
x = jump[x][i];
y = jump[y][i];
}
return jump[x][0];
}
inline int lowbit(int x){
return x & -x;
}
inline void add(int now , int num){
if(!now)
return;
while(now <= N){
Tree[now] += num;
now += lowbit(now);
}
}
inline int get(int x){
int sum = 0;
while(x){
sum += Tree[x];
x -= lowbit(x);
}
return sum;
}
void solve(int ql , int qr , int l , int r){
if(ql > qr)
return;
if(l == r){
do{
if(now[ql].ind == 3)
ans[now[ql].place] = l;
}while(++ql <= qr);
return;
}
int mid = l + r >> 1 , cntL = 0 , cntR = 0 , cnt = 0;
for(int i = ql ; i <= qr ; ++i)
if(now[i].ind == 1)
if(now[i].wei > mid){
int t = jumpToLCA(now[i].l , now[i].r);
add(dfn[now[i].l] , 1);
add(dfn[now[i].r] , 1);
add(dfn[t] , -1);
add(dfn[jump[t][0]] , -1);
potR[++cntR] = now[i];
++cnt;
}
else
potL[++cntL] = now[i];
else
if(now[i].ind == 2)
if(now[i].wei > mid){
int t = now[i].LCA;
add(dfn[now[i].l] , -1);
add(dfn[now[i].r] , -1);
add(dfn[t] , 1);
add(dfn[jump[t][0]] , 1);
potR[++cntR] = now[i];
--cnt;
}
else
potL[++cntL] = now[i];
else
if(get(dfn[now[i].l] + size[now[i].l] - 1) - get(dfn[now[i].l] - 1) == cnt)
potL[++cntL] = now[i];
else
potR[++cntR] = now[i];
for(int i = 1 ; i <= cntR ; ++i)
if(potR[i].ind == 1){
int t = potR[i].LCA;
add(dfn[potR[i].l] , -1);
add(dfn[potR[i].r] , -1);
add(dfn[t] , 1);
add(dfn[jump[t][0]] , 1);
}
else
if(potR[i].ind == 2){
int t = potR[i].LCA;
add(dfn[potR[i].l] , 1);
add(dfn[potR[i].r] , 1);
add(dfn[t] , -1);
add(dfn[jump[t][0]] , -1);
}
memcpy(now + ql , potL + 1 , sizeof(query) * cntL);
memcpy(now + ql + cntL , potR + 1 , sizeof(query) * cntR);
solve(ql , ql + cntL - 1 , l , mid);
solve(ql + cntL , qr , mid + 1 , r);
}
int main(){
N = read();
M = read();
int maxT = 0;
for(int i = 1 ; i < N ; i++){
int a = read() , b = read();
addEd(a , b);
addEd(b , a);
}
dfs(1 , 0);
for(int i = 1 ; i <= M ; i++)
if((now[i].ind = read() + 1) == 1){
now[i].l = read();
now[i].r = read();
now[i].LCA = jumpToLCA(now[i].l , now[i].r);
now[i].wei = read();
maxT = max(maxT , now[i].wei);
}
else
if(now[i].ind == 2){
now[i] = now[read()];
now[i].ind = 2;
}
else{
now[i].place = ++cntQ;
now[i].l = read();
}
solve(1 , M , 0 , maxT);
for(int i = 1 ; i <= cntQ ; i++)
printf("%d\n" , ans[i] ? ans[i] : -1);
return 0;
}
樹 (主席樹、倍增)
不妨把每一次copy出來的一棵子樹叫做大樹的一棵小樹
那么詢問的大致思路就是:如果兩個點在同一個小樹中就在模板樹上求距離;否則先跳到所在小樹的頂端,然后不斷跳小樹,直到兩個點跳到同一個小樹內,再在模板樹上求距離
首先小樹的編號比較迷,是按照原樹上的編號順序得到的。在這里我們可能需要支持:編號為\(K\)的點對應模板樹上的哪一個點,以及模板樹上的某個點對應當前小樹的哪一個點。對於第一個問題,可以發現實質就是一個求區間排名,而第二個問題是求區間第\(K\)大。使用主席樹可以完成這兩個問題。
跳小樹的過程我的倍增預處理實現是:對於每一棵小樹跳\(2^0\)步到達的是它的上一棵小樹,邊權是當前小樹的樹根到它的上一棵小樹的樹根的路徑長度。
口胡很簡單但是實現起來比較麻煩,細節很多,特別需要注意LCA的地方,還有很多容易搞混的地方,最好差不多完備地想好了實現方式再開始寫。
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<cassert>
//This code is written by Itst
using namespace std;
#define int long long
inline int read(){
int a = 0;
char c = getchar();
while(!isdigit(c))
c = getchar();
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return a;
}
const int MAXN = 1e5 + 7;
namespace SegmentTree{
struct node{
int l , r , sum;
}Tree[MAXN << 5];
int rt[MAXN] , cntN;
#define mid ((l + r) >> 1)
#define lch Tree[x].l
#define rch Tree[x].r
int insert(int t , int l , int r , int tar){
int x = ++cntN;
Tree[x] = Tree[t];
++Tree[x].sum;
if(l == r)
return x;
if(mid >= tar)
lch = insert(lch , l , mid , tar);
else
rch = insert(rch , mid + 1 , r , tar);
return x;
}
int query1(int x , int y , int l , int r , int tar){
if(l == r)
return Tree[x].sum - Tree[y].sum;
if(mid >= tar)
return query1(lch , Tree[y].l , l , mid , tar);
return Tree[lch].sum - Tree[Tree[y].l].sum + query1(rch , Tree[y].r , mid + 1 , r , tar);
}
int query2(int x , int y , int l , int r , int K){
if(l == r)
return l;
if(Tree[lch].sum - Tree[Tree[y].l].sum >= K)
return query2(lch , Tree[y].l , l , mid , K);
return query2(rch , Tree[y].r , mid + 1 , r , K - Tree[lch].sum + Tree[Tree[y].l].sum);
}
}
using SegmentTree::rt;
using SegmentTree::insert;
using SegmentTree::query1;
using SegmentTree::query2;
struct Edge{
int end , upEd;
}Ed[MAXN << 1];
int head[MAXN] , dfn[MAXN] , sz[MAXN] , jump1[MAXN][21] , dep[MAXN];
int fa[MAXN] , be[MAXN] , all[MAXN] , root[MAXN] , jump[MAXN][21][2] , Dep[MAXN];
int N , M , Q , cntEd , ts;
inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
}
void dfs(int x , int p){
sz[x] = 1;
dep[x] = dep[p] + 1;
dfn[x] = ++ts;
rt[ts] = insert(rt[ts] = rt[ts - 1] , 1 , N , x);
jump1[x][0] = p;
for(int i = 1 ; i <= 18 ; ++i)
jump1[x][i] = jump1[jump1[x][i - 1]][i - 1];
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(Ed[i].end != p){
dfs(Ed[i].end , x);
sz[x] += sz[Ed[i].end];
}
}
#define L(a , b) (dep[a] - dep[b])
inline int ciot(int x , int y){
int in = lower_bound(all , all + M + 1 , x) - all;
x = query2(rt[sz[be[in]] + dfn[be[in]] - 1] , rt[dfn[be[in]] - 1] , 1 , N , x - (in ? all[in - 1] : 0));
y = query2(rt[sz[be[in]] + dfn[be[in]] - 1] , rt[dfn[be[in]] - 1] , 1 , N , y - (in ? all[in - 1] : 0));
if(dep[x] < dep[y])
x ^= y ^= x ^= y;
int sum = L(x , y);
for(int i = 18 ; i >= 0 ; --i)
if(dep[x] - (1 << i) >= dep[y])
x = jump1[x][i];
if(x == y)
return sum;
for(int i = 18 ; i >= 0 ; --i)
if(jump1[x][i] != jump1[y][i]){
x = jump1[x][i];
y = jump1[y][i];
sum += 2 << i;
}
return sum + 2;
}
inline int calc(int x , int y){
int inx = lower_bound(all , all + M + 1 , x) - all , iny = lower_bound(all , all + M + 1 , y) - all;
if(inx == iny)
return ciot(x , y);
if(Dep[inx] < Dep[iny]){
swap(inx , iny);
swap(x , y);
}
int sum = ciot(x , root[inx]);
for(int i = 18 ; i >= 0 ; --i)
if(Dep[inx] - (1 << i) > Dep[iny]){
sum += jump[inx][i][1];
inx = jump[inx][i][0];
}
if(jump[inx][0][0] == iny)
return ciot(fa[inx] , y) + sum + 1;
sum += ciot(y , root[iny]);
if(Dep[inx] > Dep[iny]){
sum += jump[inx][0][1];
inx = jump[inx][0][0];
}
for(int i = 18 ; i >= 0 ; --i)
if(jump[inx][i][0] != jump[iny][i][0]){
sum += jump[inx][i][1] + jump[iny][i][1];
inx = jump[inx][i][0];
iny = jump[iny][i][0];
}
return ciot(fa[inx] , fa[iny]) + 2 + sum;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
N = read();
M = read();
Q = read();
for(int i = 1 ; i < N ; ++i){
int a = read() , b = read();
addEd(a , b);
addEd(b , a);
}
dfs(1 , 0);
all[0] = N;
be[0] = root[0] = 1;
for(int i = 1 ; i <= M ; ++i){
be[i] = read();
fa[i] = read();
all[i] = all[i - 1] + sz[be[i]];
root[i] = all[i - 1] + query1(rt[sz[be[i]] + dfn[be[i]] - 1] , rt[dfn[be[i]] - 1] , 1 , N , be[i]);
int t = lower_bound(all , all + i , fa[i]) - all , pos = query2(rt[sz[be[t]] + dfn[be[t]] - 1] , rt[dfn[be[t]] - 1] , 1 , N , fa[i] - (t ? all[t - 1] : 0));
Dep[i] = Dep[t] + 1;
jump[i][0][0] = t;
jump[i][0][1] = 1 + L(pos , be[t]);
for(int j = 1 ; j <= 18 ; ++j){
jump[i][j][0] = jump[jump[i][j - 1][0]][j - 1][0];
jump[i][j][1] = jump[i][j - 1][1] + jump[jump[i][j - 1][0]][j - 1][1];
}
}
while(Q--)
cout << calc(read() , read()) << '\n';
return 0;
}
序列 (單調棧、線段樹)
和HNOI2017 影魔的思想十分類似,做法也有很多,根號、log、線性都有。下面是一個離線的\(O(nlogn)\)算法
將詢問離線,按照右端點排序,每一次加一個數進去並維護每一個左端點貢獻的答案
對於“最小值”的信息,不難想到維護一個單調棧。對於不在單調棧內的數,它的貢獻已經確定,拿一個線段樹維護這些點的貢獻
對於在單調棧內的元素,不難發現:如果某一個位置一直沒有彈棧,那么它之前的所有左端點在右端點不斷\(+1\)的過程中貢獻是不會變的。所以另外拿一個線段樹,維護當前棧內的所有元素產生的貢獻。這里與普通線段樹不同的是,這棵線段樹維護的標記是貢獻的倍數。某一個數彈棧時就把它在單調棧對應的線段樹中的貢獻拿出來扔進不在單調棧內的數對應的線段樹中。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<iomanip>
#include<vector>
//This code is written by Itst
using namespace std;
#define int long long
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
}
#define mid ((l + r) >> 1)
#define lch (x << 1)
#define rch (x << 1 | 1)
const int MAXN = 1e5 + 7;
namespace SegmentTree1{
int sum[MAXN << 2] , mrk[MAXN << 2] , sz[MAXN << 2];
void init1(int x , int l , int r){
sz[x] = r - l + 1;
if(l != r){
init1(lch , l , mid);
init1(rch , mid + 1 , r);
}
}
inline void pushup(int x){sum[x] = sum[lch] + sum[rch];}
inline void mark(int x , int num){
sum[x] += num * sz[x];
mrk[x] += num;
}
inline void pushdown(int x){
mark(lch , mrk[x]);mark(rch , mrk[x]);
mrk[x] = 0;
}
void modify1(int x , int l , int r , int L , int R , int num){
if(l >= L && r <= R){
mark(x , num);
return;
}
pushdown(x);
if(mid >= L)
modify1(lch , l , mid , L , R , num);
if(mid < R)
modify1(rch , mid + 1 , r , L , R , num);
pushup(x);
}
int query1(int x , int l , int r , int L , int R){
if(l >= L && r <= R)
return sum[x];
pushdown(x);
int sum = 0;
if(mid >= L)
sum += query1(lch , l , mid , L , R);
if(mid < R)
sum += query1(rch , mid + 1 , r , L , R);
return sum;
}
}
using SegmentTree1::init1;
using SegmentTree1::modify1;
using SegmentTree1::query1;
#define INF 0x7fffffff
namespace SegmentTree2{
int sum[MAXN << 2] , all[MAXN << 2] , sz[MAXN << 2] , tag[MAXN << 2] , bon[MAXN << 2];
//bon是貢獻的倍數,all是基礎貢獻,sum=all*bon
void init2(int x , int l , int r){
sz[x] = r - l + 1;
tag[x] = INF;
if(l != r){
init2(lch , l , mid);
init2(rch , mid + 1 , r);
}
}
void pushup(int x){
sum[x] = sum[lch] + sum[rch];
all[x] = all[lch] + all[rch];
}
void mark(int x , int _tag , int _bon){
if(_tag != INF){
bon[x] = sum[x] = 0;
tag[x] = _tag;
all[x] = _tag * sz[x];
}
bon[x] += _bon;
sum[x] += all[x] * _bon;
}
void pushdown(int x){
mark(lch , tag[x] , bon[x]);
mark(rch , tag[x] , bon[x]);
tag[x] = INF;bon[x] = 0;
}
void modify2(){
mark(1 , INF , 1);
}
void set2(int x , int l , int r , int L , int R , int tag){
if(l >= L && r <= R){
mark(x , tag , 0);
return;
}
pushdown(x);
if(mid >= L)
set2(lch , l , mid , L , R , tag);
if(mid < R)
set2(rch , mid + 1 , r , L , R , tag);
pushup(x);
}
int query2(int x , int l , int r , int L , int R){
if(l >= L && r <= R)
return sum[x];
pushdown(x);
int sum = 0;
if(mid >= L)
sum += query2(lch , l , mid , L , R);
if(mid < R)
sum += query2(rch , mid + 1 , r , L , R);
return sum;
}
}
using SegmentTree2::init2;
using SegmentTree2::modify2;
using SegmentTree2::set2;
using SegmentTree2::query2;
#define PII pair < int , int >
#define PIII pair < PII , int >
#define st first
#define nd second
int stk[MAXN] , arr[MAXN];
int ans[MAXN];
int N , M , top;
vector < PIII > query;
signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
N = read();
M = read();
init1(1 , 1 , N);
init2(1 , 1 , N);
for(int i = 1 ; i <= N ; ++i)
arr[i] = read();
for(int i = 1 ; i <= M ; ++i){
int l = read() , r = read();
query.push_back(PIII(PII(r , l) , i));
}
sort(query.begin() , query.end());
int p = 0;
for(int i = 1 ; i <= N ; ++i){
while(top && arr[stk[top]] >= arr[i]){
modify1(1 , 1 , N , stk[top - 1] + 1 , stk[top] , query2(1 , 1 , N , stk[top] , stk[top]));
--top;
}
set2(1 , 1 , N , stk[top] + 1 , i , arr[i]);
stk[++top] = i;
modify2();
while(p < M && query[p].st.st == i){
int q = query[p].st.nd;
ans[query[p++].nd] = query1(1 , 1 , N , q , i) + query2(1 , 1 , N , q , i);
}
}
for(int i = 1 ; i <= M ; ++i)
cout << ans[i] << '\n';
return 0;
}
礦區 (平面圖、向量)
計算幾何……不過不用擔心精度問題確實很良心了
首先將圖中的一條無向邊轉成兩條方向相反的有向邊,那么每一條邊就會屬於一個平面。一個有界平面包含的邊就是逆時針遍歷它的邊界得到的所有有向邊。
那么確定平面的任務就等價於:給每條邊一個后繼,使得不斷訪問后繼到達的所有邊按順序剛好是圖中一個平面的逆時針遍歷。找后繼使用的方法是最小左轉法:對於每一個點將以它為起點的所有向量使用atan2按照極角從小到大排序(不能用叉積排序),對於一條邊\((s,t)\),在以\(t\)為起點的所有向量中找到\((t,s)\),它的前驅就是邊\((s,t)\)的后繼。
這樣我們可以確定所有平面,通過三角形剖分+叉積算出每一個平面的\(S\)和\(S^2\),為了精度推薦兩個都\(\times 4\)。值得注意的是會有一個面積用叉積算出來是負數,這一個平面就對應無界域。
接着是平面圖轉對偶圖:對於每一個平面在對偶圖上建一個點,對於平面圖的邊在對偶圖中在它兩邊的平面對應的點之間連一條邊,對偶圖就建立完成了。
然后在對偶圖上找到一個以無界域為根的DFS樹,記錄下生成樹的邊在平面圖上對應的邊,並維護出每個子樹的\(\sum S\)和\(\sum S^2\)。
對於每一個詢問,按照給定的順序遍歷邊,如果對應邊在對偶圖上是非樹邊就不管,否則找到這條邊在DFS樹上連接的兩點。如果這條有向邊屬於父親,那么答案減去兒子子樹的權值,否則加上兒子的權值。
最后這個生成樹相關的操作推薦自行模擬樣例進行理解,因為講也不能講得很清楚
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<cmath>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
}
#define ll long long
const int MAXN = 1.5e6 + 7;
struct Edge{
int end , upEd , ind;
}Ed[MAXN];
struct comp{
int x , y;
comp(int _x = 0 , int _y = 0) : x(_x) , y(_y){}
comp operator -(comp a){return comp(x - a.x , y - a.y);}
}pos[MAXN];
int head[MAXN] , s[MAXN] , t[MAXN] , nxt[MAXN] , be[MAXN] , link[MAXN];
ll val1[MAXN] , val2[MAXN] , sum1[MAXN] , sum2[MAXN];
int N , M , K , rt , cntEd , cnt , cntN;
vector < int > line[MAXN];
vector < int > :: iterator it;
#define PII pair < int , int >
vector < PII > edge[MAXN];
inline ll cot(comp a , comp b){
return 1ll * a.x * b.y - 1ll * a.y * b.x;
}
bool cmp(int a , int b){
comp A(pos[t[a]] - pos[s[a]]) , B(pos[t[b]] - pos[s[b]]);
return atan2(A.x , A.y) > atan2(B.x , B.y);
}
inline void add(int S , int T){
s[cnt] = S;
t[cnt] = T;
line[S].push_back(cnt);
edge[S].push_back(PII(T , cnt++));
}
inline void addEd(int a , int b , int c){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
Ed[cntEd].ind = c;
head[a] = cntEd;
}
void init(){
for(int i = 1 ; i <= N ; ++i){
sort(edge[i].begin() , edge[i].end());
sort(line[i].begin() , line[i].end() , cmp);
}
for(int i = 0 ; i < cnt ; ++i){
it = lower_bound(line[t[i]].begin() , line[t[i]].end() , i ^ 1 , cmp);
if(it == line[t[i]].begin())
it = line[t[i]].end();
nxt[i] = *--it;
}
for(int i = 0 ; i < cnt ; ++i){
if(be[i])
continue;
be[i] = ++cntN;
for(int j = nxt[i] ; j != i ; j = nxt[j]){
be[j] = cntN;
val1[cntN] += cot(pos[s[j]] - pos[s[i]] , pos[t[j]] - pos[s[i]]);
}
if(val1[cntN] < 0){
rt = cntN;
continue;
}
val2[cntN] = val1[cntN] * val1[cntN];
val1[cntN] <<= 1;
}
for(int i = 0 ; i < cnt ; ++i)
addEd(be[i] , be[i ^ 1] , i);
}
bool vis[MAXN];
void dfs(int x){
vis[x] = 1;
sum1[x] += val1[x];
sum2[x] += val2[x];
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(!vis[Ed[i].end]){
link[Ed[i].ind] = link[Ed[i].ind ^ 1] = Ed[i].end;
dfs(Ed[i].end);
sum1[x] += sum1[Ed[i].end];
sum2[x] += sum2[Ed[i].end];
}
}
inline int findEdge(int s , int t){
return lower_bound(edge[s].begin() , edge[s].end() , PII(t , 0))->second;
}
inline ll gcd(ll a , ll b){
ll r = a % b;
while(r){
a = b;
b = r;
r = a % b;
}
return b;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
N = read();
M = read();
K = read();
for(int i = 1 ; i <= N ; ++i){
pos[i].x = read();
pos[i].y = read();
}
for(int i = 1 ; i <= M ; ++i){
int S = read() , T = read();
add(S , T);
add(T , S);
}
init();
dfs(rt);
ll lastans = 0;
for(int i = 1 ; i <= K ; ++i){
int num = (lastans + read()) % N + 1 , st = (lastans + read()) % N + 1 , pre = st;
ll ans1 = 0 , ans2 = 0;
for(int j = 2 ; j <= num + 1 ; ++j){
int pos = j <= num ? (lastans + read()) % N + 1 : st, cur = findEdge(pre , pos);
if(link[cur])
if(link[cur] == be[cur]){
ans1 += sum1[link[cur]];
ans2 += sum2[link[cur]];
}
else{
ans1 -= sum1[link[cur]];
ans2 -= sum2[link[cur]];
}
pre = pos;
}
ll t = gcd(ans2 , ans1);
ans2 /= t;
ans1 /= t;
cout << ans2 << ' ' << ans1 << '\n';
lastans = ans2;
}
return 0;
}
大數 (莫隊)
不妨將大數看做一個\(base=10\)的哈希
設\(pre_i\)表示前\(i\)個數構成的大數的值,\(num_{i,j}\)表示第\(i\)到\(j\)個數構成的大數的值,那么有\(num_{i,j} = pre_j - pre_i \times 10^{j-i} = 10^j(\frac{pre_j}{10^j} - \frac{pre_i}{10^i})\)
如果\(P \not\mid 10\),那么\(10^j \not\equiv 0 \mod P\),且在\(\mod P\)意義下存在\(10^{-1}\)。那么把所有的\(\frac{pre_i}{10^i}\)存起來離散化,然后使用莫隊統計結果。
如果\(P \mid 10\),那么只需要一個數的末位是\(P\)的倍數,這個數就可以滿足條件。
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cmath>
#include<cstring>
//This code is written by Itst
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
while(!isdigit(c))
c = getchar();
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return a;
}
#define ll long long
const int MAXN = 1e5 + 7;
int lsh[MAXN] , val[MAXN] , Hash[MAXN] , poww10[MAXN] , inv10[MAXN] , cnt[MAXN];
int N , M , T , cntL , MOD;
ll ans[MAXN];
ll cur;
char s[MAXN];
struct query{
int l , r , ind;
bool operator <(const query a)const{
return l / T == a.l / T ? r < a.r : l < a.l;
}
}now[MAXN];
inline int poww(long long a , int b){
int times = 1;
while(b){
if(b & 1)
times = times * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return times;
}
inline char getc(){
char c = getchar();
while(!isdigit(c))
c = getchar();
return c;
}
void init(){
poww10[0] = inv10[0] = 1;
for(int i = 1 ; i <= N ; ++i)
poww10[i] = poww10[i - 1] * 10ll % MOD;
inv10[1] = poww(10 , MOD - 2);
for(int i = 2 ; i <= N ; ++i)
inv10[i] = 1ll * inv10[i - 1] * inv10[1] % MOD;
}
inline void add(int pos){cur += cnt[Hash[pos]]++;}
inline void del(int pos){cur -= --cnt[Hash[pos]];}
inline void add2(int pos){if(s[pos] % MOD == 0)cur += pos , ++cnt[0];}
inline void del2(int pos){if(s[pos] % MOD == 0)cur -= pos , --cnt[0];}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
MOD = read();
scanf("%s" , s + 1);
N = strlen(s + 1);
T = sqrt(N);
init();
for(int i = 1 ; i <= N ; ++i){
val[i] = (val[i - 1] * 10ll + (s[i] -= 48)) % MOD;
lsh[i] = Hash[i] = 1ll * val[i] * inv10[i] % MOD;
}
sort(lsh , lsh + N + 1);
cntL = unique(lsh , lsh + N + 1) - lsh - 1;
for(int i = 0 ; i <= N ; ++i)
Hash[i] = lower_bound(lsh , lsh + cntL + 1 , Hash[i]) - lsh;
M = read();
for(int i = 1 ; i <= M ; ++i){
now[i].l = read();
now[i].r = read();
now[i].ind = i;
}
sort(now + 1 , now + M + 1);
int L = 1 , R = 0;
if(10 % MOD)
add(0);
for(int i = 1 ; i <= M ; ++i){
while(L > now[i].l)
10 % MOD ? add((--L) - 1) : add2(--L);
while(R < now[i].r)
10 % MOD ? add(++R) : add2(++R);
while(L < now[i].l)
10 % MOD ? del((L++) - 1) : del2(L++);
while(R > now[i].r)
10 % MOD ? del(R--) : del2(R--);
ans[now[i].ind] = 10 % MOD ? cur : cur - 1ll * cnt[0] * (L - 1);
}
for(int i = 1 ; i <= M ; ++i)
cout << ans[i] << '\n';
return 0;
}
