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(qsqrt{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
" , 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()) << '
';
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] << '
';
return 0;
}
矿区 (平面图、向量)
计算几何……不过不用担心精度问题确实很良心了
首先将图中的一条无向边转成两条方向相反的有向边,那么每一条边就会属于一个平面。一个有界平面包含的边就是逆时针遍历它的边界得到的所有有向边。
那么确定平面的任务就等价于:给每条边一个后继,使得不断访问后继到达的所有边按顺序刚好是图中一个平面的逆时针遍历。找后继使用的方法是最小左转法:对于每一个点将以它为起点的所有向量使用atan2按照极角从小到大排序(不能用叉积排序),对于一条边((s,t)),在以(t)为起点的所有向量中找到((t,s)),它的前驱就是边((s,t))的后继。
这样我们可以确定所有平面,通过三角形剖分+叉积算出每一个平面的(S)和(S^2),为了精度推荐两个都( imes 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 << '
';
lastans = ans2;
}
return 0;
}
大数 (莫队)
不妨将大数看做一个(base=10)的哈希
设(pre_i)表示前(i)个数构成的大数的值,(num_{i,j})表示第(i)到(j)个数构成的大数的值,那么有(num_{i,j} = pre_j - pre_i imes 10^{j-i} = 10^j(frac{pre_j}{10^j} - frac{pre_i}{10^i}))
如果(P otmid 10),那么(10^j otequiv 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] << '
';
return 0;
}