• [冲刺国赛2022] 模拟赛9


    被创与地震

    题目描述

    \(n\) 个点 \(m\) 条边的无向图,初始时所有边都没有被激活,任意两点都是不连通的。每个点 \(i\) 有点权 \(a_i\),如果一条边 \((u,v,c)\) 满足 \(u\) 联通块的 \(a\) 值和 \(+\) \(v\) 连通块的 \(a\) 值和 \(\geq c\),则可以激活边 \((u,v)\),以后都可以通过这条边。

    但如果 \((u,v)\) 已经在同一条联通块内则不可以激活这条边。问最大激活边数,我们按顺序写下激活边的编号,在激活边数最大的情况下,还要最小化这个序列的字典序。

    \(n\leq 10^5,m\leq 2\cdot 10^5,s_i,a_i\leq 10^6\)

    解法

    观察到最大激活边数和加入哪条边以及什么时候加入都无关,所以每次直接加入编号最小的可加入的边。

    考虑启发式合并,每次只需要考虑从这个连通块连出去的边。我们想要取出合法的边,但是由于不知道另一边的情况所以这并不好做。考虑放宽限制,每次可以多检查一些边,但是一条边最多被检查的次数是较小的。

    不妨构造一种检查方式,使得至多检查 \(O(\log s)\) 次之后这条边一定合法。可以想到检查一次之后让值域折半,可以对每条边设置一个阈值 \(lim\),如果当前连通块的权值和超过 \(lim\) 就检查这条边。

    对于点 \(u\) 连出去的边 \((u,v,c)\),把 \(lim\) 设置为 \(a[u]+\lceil\frac{c-a[u]-a[v]}{2}\rceil\),表示 \(u\) 的权值必须要 \(\geq lim\) 才有可能从 \(u\) 处检查这条边,要不然检查是无意义的(因为这条边的两边都小于需要增量的一半)

    在检查这条边的时候更新它的 \(lim\),时间复杂度 \(O(n\log^2n+n\log A)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <queue>
    #include <set>
    using namespace std;
    const int M = 200005;
    const int inf = 0x3f3f3f3f;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,x[M],y[M],c[M],fa[M],a[M];
    struct node
    {
        int v,d,i;
        bool operator < (const node &b) const
            {return d==b.d?i<b.i:d<b.d;}
    };set<node> s[M];vector<int> ans;
    priority_queue<int,vector<int>,greater<int>>q;
    int find(int x)
    {
        if(x!=fa[x]) fa[x]=find(fa[x]);
        return fa[x];
    }
    void merge(int u,int v)
    {
        if(s[u].size()<s[v].size()) swap(s[u],s[v]);
        for(node t:s[v]) s[u].insert(t);
        fa[v]=u;a[u]=min(a[u]+a[v],inf);
        for(auto it=s[u].begin();!s[u].empty();it=s[u].begin())
        {
            if(it->d>a[u]) break;
            int v=find(it->v),i=it->i;
            if(u==v) {s[u].erase(it);continue;}
            if(a[u]+a[v]>=c[i])
            {
                q.push(i);
                s[u].erase(it);
                continue;
            }
            int d=(c[i]-a[u]-a[v]+1)/2;
            s[u].erase(node{v,it->d,i});
            s[v].erase(node{u,it->d,i});
            s[u].insert(node{v,a[u]+d,i});
            s[v].insert(node{u,a[v]+d,i});
        }
    }
    signed main()
    {
        freopen("earthquake.in","r",stdin);
        freopen("earthquake.out","w",stdout);
        n=read();m=read();
        for(int i=1;i<=n;i++) a[i]=read(),fa[i]=i;
        for(int i=1;i<=m;i++)
        {
            x[i]=read();y[i]=read();c[i]=read();
            if(a[x[i]]+a[y[i]]>=c[i]) q.push(i);
            else
            {
                int d=(c[i]-a[x[i]]-a[y[i]]+1)/2;
                s[x[i]].insert(node{y[i],a[x[i]]+d,i});
                s[y[i]].insert(node{x[i],a[y[i]]+d,i});
            }
        }
        while(!q.empty())
        {
            int t=q.top();q.pop();
            if(find(x[t])==find(y[t])) continue;
            ans.push_back(t);
            merge(find(x[t]),find(y[t]));
        }
        printf("%d\n",ans.size());
        for(int x:ans) printf("%d ",x);
        puts("");
    }
    

    偶耶与时光机

    题目描述

    数轴上有 \(2n+2\) 个点,分别是 \(0,1,2....2n,2n+1\),神 \(\tt OUYE\) 想要从 \(0\) 走到 \(2n+1\),每次他会从 \(i\) 走到 \(i+1\)

    \(1,2...2n\)\(2n\) 个点被建立了传送门,一共有 \(n\) 个传送门,每个点都在且仅在一个传送门中。传送门的规则是:对于一个传送门 \((i,j)\),如果从 \(i-1\) 走到 \(i\),则会被强制传送到 \(j\);如果从 \(j-1\) 走到 \(j\),则会被强制传送到 \(i\)

    现在给定 \(m\) 个整数,\(a_i\) 表示神 \(\tt OUYE\) 徒步且仅徒步经过了 \((a_i,a_i+1)\) 这些小段(区别于传送);请你构造传送门的方案满足 \(\tt OUYE\),或者是告诉 \(\tt OUYE\) 这不可能。

    \(n\leq 10^5,m\leq 2\cdot 10^5\)

    解法

    首先考虑所有小段都被经过的情况,发现 \(n\) 是偶数的情况容易构造,即把相邻四个分为一组;\(n\) 为奇数的情况手玩发现无解(懒得证明了)

    对于有小段没有被经过的情况,我们把点分成下列四类:

    • 左右两边的小段都被经过了,记为 A
    • 左右两边的小段都没有被经过,记为 N
    • 左边的小段被经过了,记为 L
    • 右边的小段被经过了,记为 R

    A 的个数为奇数显然无解,可以现特判掉。

    首先考虑 A 的个数为 \(4\) 的倍数的情况,在这种情况下,我们首先把 LR 贪心地匹配(最近的两个匹配在一起),N 之间可以任意匹配。这样 LR 可以达到跳过中间段不经过的效果,剩下的 A 可以按照相邻四个一组的方法构造。

    那么 A 的个数不为 \(4\) 的个数就一定无解吗?其实我们可以把分开的 LR...LR 等效成 A...A,方法是把外层的 LR 和内层的 RL 连边,这样走到第一个 L 就会从第二个 R 出来,走到第二个 L 就会从第一个 R 出来,所以这和 A...A 是等效的。

    那么现在 A 的个数可以看成 \(4\) 的倍数个,我们首先把这个含有等效 A 的组分配好。方法就是找到内部的第一个 A,然后找到外部左边的第一个 A 或者外部右边的第一个 A 然后分为一组。剩下的 A 用相邻四个一组的方法构造。

    时间复杂度 \(O(n)\)

    #include <cstdio>
    #include <vector>
    #include <cassert>
    #include <iostream>
    #include <set>
    using namespace std;
    const int M = 200005;
    #define pii pair<int,int>
    #define pb push_back
    #define fi first
    #define se second
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,a[M];vector<int> s;
    set<int> b;vector<pii> ans;
    signed main()
    {
    	freopen("shuttle.in","r",stdin);
    	freopen("shuttle.out","w",stdout);
    	n=read()<<1;m=read();
    	for(int i=1;i<=m;i++) a[read()]=1;
    	pii p(0,0),q(0,0);
    	for(int i=1,l1=0,l2=0;i<=n;i++)
    	{
    		if(a[i] && a[i-1]) b.insert(i);
    		else if(!a[i] && !a[i-1])
    		{
    			if(l1) ans.pb({l1,i}),l1=0;
    			else l1=i;
    		}
    		else
    		{
    			if(!l2) {l2=i;continue;}
    			if(!p.fi) p={l2,i};
    			else if(!q.fi && b.size() && *b.rbegin()>p.se)
    				q={l2,i};
    			else ans.pb({l2,i});
    			l2=0;
    		}
    	}
    	if(b.size()&1) {puts("No");return 0;}
    	if(b.size()%4==0)
    	{
    		if(p.fi) ans.pb(p);
    		if(q.fi) ans.pb(q);
    	}
    	else
    	{
    		if(!q.fi) {puts("No");return 0;}
    		ans.pb({p.fi,q.se});
    		ans.pb({p.se,q.fi});
    		auto i=lower_bound(b.begin(),b.end(),p.fi);
    		auto j=lower_bound(b.begin(),b.end(),q.fi);
    		if(i!=b.begin())
    		{
    			int v=*i;i--;int u=*i;
    			ans.pb(make_pair(u,v));
    			b.erase(u);b.erase(v);
    		}
    		else if(j!=b.end())
    		{
    			int u=*i,v=*j;
    			ans.pb({u,v});
    			b.erase(u);b.erase(v);
    		}
    		else {puts("No");return 0;}
    	}
    	for(int x:b) s.pb(x);
    	for(int i=0;i<s.size();i+=4)
    	{
    		ans.pb({s[i],s[i+2]});
    		ans.pb({s[i+1],s[i+3]});
    	}
    	puts("Yes");
    	for(auto x:ans) printf("%d %d\n",x.fi,x.se);
    }
    

    杀老师与赌场

    题目描述

    杀老师有 \(<1\) 的钱,他想赚够一块钱买个棒棒糖。

    此时正好有一个赌场,如果杀老师投入 \(x\) 的钱,有 \(p\) 的几率赚到 \(x\),有 \(1-p\) 的几率血本无归。

    请问最优策略下,杀老师能吃到棒棒糖的期望是多少。杀老师初始的钱数由一个分数 \(\frac{x}{y}\) 给出。

    \(x,y\leq 10^6\)

    \(\tt subtask\)\(y\)\(2\) 的某个次幂。

    解法

    首先考虑 \(y\)\(2^k\) 的情况,最优策略是把 \(\frac{x}{y}\) 写成二进制小数,比如 \(0.010101...\),然后从最低位一路操作上来。正确性大家可以感性理解一下(因为这结论我都能发现)

    计算答案考虑 \(dp\),设 \(dp[i]\) 表示操作到第 \(i\) 位可以向上进位的概率。那么答案是 \(dp[1]\),转移:

    • 如果这一位是 \(1\)\(dp[i]=dp[i+1]+p\cdot (1-dp[i+1])\)
    • 如果这一位是 \(0\)\(dp[i]=dp[i+1]\cdot p\)

    对于 \(y\) 不是 \(2^k\) 的情况,可以把 \(\frac{x}{y}\) 写成循环二进制小数,当出现相同的余数是就找到的循环,所以根据鸽笼原理,循环节的长度是 \(O(y)\) 的。可以把转移写成 \(g(x)=kx+b\) 的形式,一次函数的复合规则是 \(f(g(x))=k_1(k_2x+b_2)+b_1\),我们先把非循环部分的复合函数 \(f(x)\) 和循环部分的复合函数 \(g(x)\) 求出来,那么要求的函数是:

    \[f(g(g(...g(0)...))) \]

    可以用无穷级数的方式去解决 \(g(g(...g(0)...))\),即:

    \[g(g(...g(0)...))=b+kb+k^2b+k^3b.....=\frac{b}{1-k} \]

    那么暴力找到循环节就可以计算了,时间复杂度 \(O(y)\)

    #include <cstdio>
    const int M = 2000005;
    #define int long long
    const int MOD = 1e9+7;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,x,y,p,n,vis[M],a[M],k[M],b[M],to[M],s[M];
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    int solve(int u)
    {
    	if(vis[u])
    	{
    		int K=1,B=0;
    		while(1)
    		{
    			int v=s[n];
    			K=K*k[v]%MOD;
    			B=B*k[v]%MOD;
    			B=(B+b[v])%MOD;
    			if(s[n]==u) break;
    			n--;
    		}
    		return B*qkpow(MOD+1-K,MOD-2)%MOD;
    	}
    	s[++n]=u;vis[u]=1;
    	return (b[u]+k[u]*solve(to[u]))%MOD;
    }
    void work()
    {
    	x=read();y=read();
    	p=read()*qkpow(read(),MOD-2)%MOD;
    	vis[0]=0;
    	for(int i=1;i<y;i++)
    	{
    		vis[i]=0;
    		if(i*2<y) to[i]=i*2,k[i]=p,b[i]=0;
    		else to[i]=i*2-y,k[i]=(MOD+1-p)%MOD,b[i]=p;
    	}
    	printf("%d\n",solve(x));
    }
    signed main()
    {
    	freopen("bat.in","r",stdin);
    	freopen("bat.out","w",stdout);
    	T=read();
    	while(T--) work();
    }
    
  • 相关阅读:
    《JavaScript高级程序设计》读书笔记 ---Object 类型
    《JavaScript高级程序设计》读书笔记 ---变量、作用域和内存问题小结
    《JavaScript高级程序设计》读书笔记 ---执行环境及作用域
    《JavaScript高级程序设计》读书笔记 ---基本类型和引用类型的值
    《JavaScript高级程序设计》读书笔记 ---函数
    《JavaScript高级程序设计》读书笔记 ---基本概念小结
    《JavaScript高级程序设计》读书笔记 ---语句
    《JavaScript高级程序设计》读书笔记 ---if语句
    Vue2.0组件间数据传递
    UIButton快速点击,只执行最后一次
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16398027.html
Copyright © 2020-2023  润新知