• 【loj



    description

    题目太长,去loj看吧。

    solution

    对于给定的点 x 与点集 s,可以通过 modify s 中的所有点判断 s 到 x 的边数奇偶性。

    进一步地,如果是奇数条边,可以通过二分找到任意一条边。这样可以 O(mlogm) 次询问找到所有边。

    注意到 modify 重复次数太多,我们考虑整体二分。

    但是整体二分又有问题,比如可能二分到同一条边。
    解决方法是只找一个点向排在它前面的点的边。但是要满足向前为奇数又比较困难。

    “题目保证测试所使用的图在交互开始之前已经完全确定,而不会根据和你的程序的交互动态构造。”

    于是我们随机化排列,可以猜测到一个点往前连的边的数量是奇是偶的概率几乎是相等的(貌似最劣概率是 1/3?)。

    然后就发生了这种事情:

    有几个优化:
    (1)check 操作可以帮助排除无用点。
    (2)已有的边需要在 modify 时消除影响。

    这两个是主要的。还有就是我自己写的时候遇到的问题:
    (3)二分前不需要先判断点向前是否连奇数条边,直接硬刚。这样可以少一半的 modify。
    (4)如果点 x <= mid,则不需要 query 直接往左边递归。这样可以少很多的 query。
    (5)随机数种子使用系统库默认种子。


    测试点 2~5 需要写暴力。

    测试点 6~9(A) 与测试点 10~11(B) 不能使用 check,但是依然可以二分,只是跑二分之前不使用 random_shuffle。

    accepted code

    #include "explore.h"
    #include <vector>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    const int MAXN = 200000;
    
    vector<int>G[MAXN + 5];
    bool tag[MAXN + 5], nw[MAXN + 5]; int mcnt;
    bool can_check;
    void answer(int u, int v) {
    	G[u].push_back(v), G[v].push_back(u);
    	mcnt++, report(u, v);
    	if( can_check ) {
    		if( check(u) ) tag[u] = true;
    		if( check(v) ) tag[v] = true;
    	}
    }
    bool is_change(int x) {
    	if( query(x) != nw[x] ) {
    		nw[x] ^= 1;
    		return true;
    	} else return false;
    }
    void update(int x) {
    	for(unsigned i=0;i<G[x].size();i++)
    		nw[G[x][i]] ^= 1;
    	modify(x);
    }
    
    int b[MAXN + 5], m, a[MAXN + 5], cnt;
    void get(int L, int R, int l, int r) {
    	if( l > r ) return ;
    	if( L == R ) {
    		for(int i=l;i<=r;i++)
    			if( L < b[i] ) answer(a[L], a[b[i]]);
    		return ;
    	}
    	int M = (L + R) >> 1, tot = l;
    	for(int i=l;i<=r;i++) nw[a[b[i]]] = false;
    	for(int i=L;i<=M;i++) update(a[i]);
    	for(int i=l;i<=r;i++)
    		if( b[i] <= M || is_change(a[b[i]]) ) swap(b[tot++], b[i]);
    	for(int i=L;i<=M;i++) update(a[i]);
    	get(L, M, l, tot - 1), get(M + 1, R, tot, r);
    }
    void solve1(int N, int M) {
    	for(int i=0;i<N-1;i++) {
    		modify(i);
    		for(int j=i+1;j<N;j++)
    			if( is_change(j) ) report(i, j);
    	}
    }
    void explore(int N, int M) {
    	if( N <= 500 ) solve1(N, M);
    	else if( N % 10 == 8 ) {
    		int cnta = 0, cntb = 0;
    		for(int i=0;i<N;i++) {
    			if( query(i) ) b[++cntb] = i;
    			else modify(i), a[++cnta] = i;
    		}
    		for(int i=1;i<=cnta;i++) modify(a[i]);
    		for(int i=1;i<=cntb;i++) a[++cnta] = b[i], b[i] = cnta;
    		get(1, N / 2, 1, m = N / 2);
    	} else if( N % 10 == 7 ) {
    		for(int i=1;i<=N;i++) a[i] = i - 1, b[i] = i;
    		get(1, N, 1, m = N);
    	} else {
    		can_check = true;
    		for(int i=0;i<N;i++)
    			G[i].push_back(i);
    		
    		while( mcnt != M ) {
    			cnt = 0;
    			for(int i=0;i<N;i++)
    				if( !tag[i] ) a[++cnt] = i;
    			
    			random_shuffle(a + 1, a + cnt + 1);
    			for(int i=1;i<=cnt;i++) nw[a[i]] = false;
    			for(int i=1;i<=cnt;i++) b[i] = i;
    			get(1, cnt, 1, m = cnt);
    		}
    	}
    }
    

    details

    感觉隔壁 JOI 系列比赛的交互题也很喜欢整体二分。

    树的部分还有基于异或和按位讨论的非随机算法:
    (1)先找到与点 x 相邻的点的异或和,记为 sum[x]。这部分按位 modify 就可以 O(nlogn) 做。
    (2)如果 x 与 sum[x] 有边相连,且 x 的度数为 1(用 check 操作判断),则 x 显然为叶子。每次剥去叶子并尝试剥去与叶子相邻的点,可以做到 O(n + m) 的复杂度。

    (为什么我当时啥也想不到啊)

  • 相关阅读:
    【一篇文章就够了】Apollo
    【一篇文章就够了】Redis入门
    使用IDEA学习廖雪峰官方Git教程
    SourceTree的使用介绍
    【黑马旅游网】总结
    Entity Framework数据迁移命令使用总结
    leetcode editor
    IDEA个性化配置
    mysql5.7驱动(tableau,excel)
    正则表达式(1)
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/13208974.html
Copyright © 2020-2023  润新知