HNOI2016 矿区

题目大意

给定一个 N 个点M条边的平面图 G 。有Q个询问,每次询问平面图上的一个区域 A ,(逆时针地给定多边形的点集),你需要求出PAS(P)2PAS(P) S(P) 表示 P 这个面的面积。

数据范围

N2105,M3N6
2106

题解

首先我们要构成 G 的对偶图,设为G,以无穷域为根,构出 G 的生成树 T 。以无穷域为根是有原因的,等下会讲。
接下来,对于一个询问A,我们依次在 T 中将A中相邻点的连边在 G 中对应的对偶边删掉(假如不在生成树中则不管),接下来 T 会裂成若干个联通块,假如一个联通块被我们选出来的边围住了,那么我们就可以将这个联通块的答案统计进去了。但怎么定义围住了?设我们当前要删掉的边为(u,v),注意边是有方向的,就是一个向量,那么假如在树中, u v的父亲,那么我们就加上 Subv 的信息,若 v u的父亲,那么我们减去 Subu 的信息。那么我们最终就可以得到所有合法联通块的信息了。
这样为什么是对的?
1. 不会计多。假如一个面 u 不在A中,但最后却被统计入了某个合法的联通块中,那么这个联通块中必然会存在一条边 (a,b) 满足 a A中而 b 不在A中,但这样的边我们一开始就会删掉,因此矛盾。
2. 不会计少。注意到我们唯一计少的情况就是存在一个面 u ,使得u A 中,但u不在任一合法的联通块中。注意到一个联通块的边界必然是由 A 的边界面所构成,而u A 中,那么u在生成树中至少与一个 A 中的边界面联通(把他围住了),所以u必然在某个联通块中,矛盾。
因此,我们就证明了这个算法是正确的。以无穷域为根就是因为无穷域不可能在 A 中,那么就能保证每个在A中的面在生成树中必然存在祖先边,也就是与某个边界面联通。
那么这题就比较简单了,一开始先构出对偶图,以无穷域为根做生成树,然后对于询问扫一下每条边即可。
由于构出对偶图需要每个点对每条边按极角排序,所以总的复杂度为 O(MlogM+)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#define fe first
#define se second

using namespace std;

typedef unsigned long long LL;
typedef pair<int,int> P;

const int MAXN = 2000005,HAS_1 = 3999647,HAS_2 = 3983633;

LL operator ^(const P &a,const P &b) {return LL(a.fe) * b.se - LL(a.se) * b.fe;}

vector<int> Lk[MAXN],E[MAXN];
LL S[MAXN],Are[MAXN];
long long Ar[MAXN];
bool Walk[MAXN],In[MAXN];
double Ang[MAXN];
P Po[MAXN];
int To[MAXN],Bel[MAXN],Rank[MAXN],N,M,K,tot,cnt;
int V[HAS_1][3];

void Push(LL a,int ref)
{
    int l = a % HAS_1;
    for(;V[l][2];l = (l + 1) % HAS_1);
    V[l][0] = a % HAS_1,V[l][1] = a % HAS_2,V[l][2] = ref;
}

int Find(LL a)
{
    int l = a % HAS_1,l1 = a % HAS_2,ll = a % HAS_1;
    for(;V[l][0] != ll ||  V[l][1] != l1;l = (l + 1) % HAS_1);
    return V[l][2];
}

void Link(int u,int v)
{
    To[++ tot] = v,Ang[tot] = atan2(Po[v].se - Po[u].se,Po[v].fe - Po[u].fe),Lk[u].push_back(tot);
    Push(u * 1ll * N + v,tot);
}

inline bool cmp(const int &a,const int &b) {return Ang[a] < Ang[b];}

void Dfs(int Now,int Pr)
{
    for(;!Walk[Now];)
    {
        Walk[Now] = 1;
        Bel[Now] = cnt;
        Ar[cnt] += (Po[To[Now]] ^ Po[Pr]);
        Pr = To[Now];
        Now = Lk[To[Now]][(Rank[Now ^ 1] + 1) % Lk[To[Now]].size()];
    }   
}

void Dfs_T(int Now)
{
    static int Q[MAXN];
    Q[1] = Now;
    int fi = 1,en = 1;
    for(;fi <= en;fi ++)
    {
        Now = Q[fi];
        Walk[Now] = 1;
        S[Now] = Are[Now] * Are[Now];
        for(int i = 0;i < E[Now].size();i ++)
        {
            int v = E[Now][i];
            if (Walk[Bel[v ^ 1]]) continue;
            Walk[Bel[v ^ 1]] = 1;
            In[v] = 1;
            Q[++ en] = Bel[v ^ 1];
        }
    }
    for(;en;en --)
    {
        Now = Q[en];
        for(int i = 0;i < E[Now].size();i ++)
        {
            int v = E[Now][i];
            if (!In[v]) continue;
            S[Now] += S[Bel[v ^ 1]],Are[Now] += Are[Bel[v ^ 1]];
        }
    }
}

LL Gcd(LL a,LL b) {return b ? Gcd(b,a % b) : a;}

int Search(int u,int v) {return Find(u * 1ll * N + v);}

template<class T>
void read(T &x)
{
    char c;
    bool f = 0;
    while (c = getchar(),(c < '0' || c > '9') && c != '-');
    if (c == '-') f = 1,x = 0; else x = c - 48;
    while (c = getchar(),(c >= '0' && c <= '9')) x = x * 10 + c - 48;
    if (f) x = -x;
}

int main()
{   
    scanf("%d%d%d", &N, &M, &K);
    for(int i = 1;i <= N;i ++) read(Po[i].fe),read(Po[i].se);
    tot = 1;
    for(int i = 1;i <= M;i ++)
    {
        int u,v;
        read(u),read(v);
        Link(u,v),Link(v,u);
    }
    for(int i = 1;i <= N;i ++) stable_sort(Lk[i].begin(),Lk[i].end(),cmp);
    for(int i = 1;i <= N;i ++)
        for(int j = 0;j < Lk[i].size();j ++) Rank[Lk[i][j]] = j;
    for(int i = 1;i <= N;i ++)
        for(int j = 0;j < Lk[i].size();j ++)
        {
            int v = Lk[i][j];
            if (Walk[v]) continue;
            ++ cnt;
            Dfs(v,i);
        }
    for(int i = 2;i <= tot;i ++)
    {
        int u = To[i],v = To[i ^ 1];
        E[Bel[i]].push_back(i);
    }
    memset(Walk,0,sizeof Walk);
    int Root = 0;
    for(int i = 1;i <= cnt;i ++) if (Ar[i] < 0) Root = i,Ar[i] = 0;
    for(int i = 1;i <= cnt;i ++) Are[i] = Ar[i];
    Dfs_T(Root);
    LL lst = 0;
    for(int i = 1;i <= K;i ++)
    {
        static long long Z[MAXN];
        long long C = 0;
        read(C);
        C = (C + lst) % N + 1;
        for(int j = 1;j <= C;j ++)
        {
            read(Z[j]);
            Z[j] = (Z[j] + lst) % N + 1;
        }
        LL Anstop = 0,Ansbot = 0;
        bool ok = 0;
        for(int j = 1;j <= C;j ++)
        {
            int u = Z[j],v = Z[j % C + 1];
            int ref = Search(u,v);
            if (!(In[ref] || In[ref ^ 1])) continue;
            if (In[ref]) ok = 1,Anstop += S[Bel[ref ^ 1]],Ansbot += Are[Bel[ref ^ 1]]; else
                Anstop -= S[Bel[ref]],Ansbot -= Are[Bel[ref]];
        }
        Ansbot *= 2;
        LL gcd = Gcd(Anstop,Ansbot);
        Anstop /= gcd,Ansbot /= gcd;
        printf("%lld %lld\n", Anstop,Ansbot);
        lst = Anstop % N;
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值