2019.01.30【NOIP提高组】模拟 B 组

1.QYQ的图

Description

给你一个n个点,m条边的无向图,每个点有一个非负的权值 c i c_i ci,现在你需要选择一些点,使得每一个点都满足:
如果这个点没有被选择,则与它有边相连的所有点都必须被选择。
问:满足上述条件的点集中,所有选择的点的权值和最小是多少?
QYQ很快就解决了这个问题,但是他已经回到了左下角……没有留下答案,现在只好请你来解决这个问题啦!

Input

从文件graph.in中输入数据。
输入的第一行包含两个整数n,m
输入的第二行包含n个整数,其中第i个整数代表 c i c_i ci
输入的第三行到第m+2行,每行包含两个整数u,v,代表点u和点v之间有一条边

Output

输出到文件graph.out中。
输出的第一行包含一个整数,代表最小的权值和

Data Constraint

对于 20 % 20\% 20% 的数据: n ≤ 10 n\leq10 n10
对于 40 % 40\% 40%的数据: n ≤ 20 n\leq 20 n20
对于 100 % 100\% 100%的数据: 1 ≤ n ≤ 50 1\leq n\leq50 1n50 1 ≤ m ≤ 500 1\leq m\leq500 1m500 0 ≤ c ≤ 1000 0\leq c\leq1000 0c1000
图中可能会有重边,自环。
点的编号为 1 → n 1\to n 1n

Solutions

D F S \large DFS DFS

Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=60;
int tot,in[N],ans,w[N],flag[N],n,maxs,m,ls[N];
bool v[N],zh[N]; 
struct node{
	int to,next;
}a[N*20];
void addl(int x,int y)
{
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;
}
void dfs(int x)
{
	in[++tot]=x;v[x]=1;
	for(int i=ls[x];i;i=a[i].next)
	  if(!v[a[i].to])
		dfs(a[i].to);
}
void get_ans(int dep,int z)
{
	if(z>maxs) return;
	if(dep>tot)
	{
		maxs=min(maxs,z);
		return;
	}
	get_ans(dep+1,z+w[in[dep]]);
	if(!flag[in[dep]]&&!zh[in[dep]]){
		for(int i=ls[in[dep]];i;i=a[i].next)
			flag[a[i].to]++;
		get_ans(dep+1,z);
		for(int i=ls[in[dep]];i;i=a[i].next)
			flag[a[i].to]--;
	}
}
int main()
{
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		addl(x,y);addl(y,x);
		if(x==y) zh[x]=1;
	}
	for(int i=1;i<=n;i++)
	{
		maxs=2147483647;tot=0; 
		if(v[i]) continue;
		dfs(i);
	    get_ans(1,0);
	    ans+=maxs;
	}
	printf("%d",ans);
}

2.n染色

Description

WYF画了一个极为不规则的n边形,画面太美简直不看,没有任意两条边长度是相等的。因为形状太难看了,做他同桌的CWQ看不下去了,趁着WYF上厕所的时间准备用他书包里的m种颜色的彩笔给n边形的边上色。但由于WYF画的实在太大,CWQ不知如何下手,他想知道他有多少种染色方法,能够使得每两条相邻 边不同色。你只需输出答案模 1 e 9 + 7 1e9+7 1e9+7的结果。

Input

一行,仅包含两个正整数n和m。

Output

一个正整数,表示答案模10^9+7的结果

Data Constraint

对于 50 % 50\% 50%的数据, 3 ≤ n ≤ 100000 , 3 ≤ m ≤ 10 3≤n≤100000,3≤m≤10 3n1000003m10
对于 100 % 100\% 100%的数据, 3 ≤ n ≤ 1 0 18 , 3 ≤ m ≤ 50 3≤n≤10^{18},3≤m≤50 3n10183m50

Solution

设有 n 个点时,方案数为 A [ n ] A[n] A[n]
一个一个点考虑,加入第 n n n 个点时:
其左右不同,则插入之前的方案数为 A [ n − 1 ] A[n-1] A[n1],第 n n n 个点可以有 m 2 m2 m2 种选择。
其左右相同,在插入之前,则删掉其中一个点之后的的 n − 2 n-2 n2 个点
是满足要求的方案,数量为 A [ n − 2 ] A[n-2] A[n2],则 n n n 的颜色只需与相邻的不同,
m − 1 m-1 m1 种方案。
综上,递推公式为 A [ n ] = A [ n − 1 ] ∗ ( m − 2 ) + A [ n − 2 ] ∗ ( m − 1 ) A[n] = A[n-1] * (m-2) + A[n-2] * (m-1) A[n]=A[n1](m2)+A[n2](m1)
通项公式(特征根): A [ n ] = ( m − 1 ) n + ( m − 1 ) ∗ ( − 1 ) n A[n] = (m-1)^n + (m-1)*(-1)^n A[n]=(m1)n+(m1)(1)n
(KSM)

Code

#include<cstdio>
#define ll long long
#define mo 1000000007
using namespace std;
ll n,m,ans=0;
int a[100010];

ll ksm(ll x,ll y)
{
	ll s=1;
	while (y)
	{
		if (y & 1) s=s*x%mo;
		x=x*x%mo,y>>=1;
	}
	return s;
}

int main()
{
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	ans=ksm(m-1,n)+(m-1)*ksm(-1,n);
	printf("%lld\n",ans%mo);
	return 0;
}

游戏

Description

WYF从小就爱乱顶,但是顶是会造成位移的。他之前水平有限,每次只能顶出k的位移,也就是从一个整点顶到另一个整点上。我们现在将之简化到数轴上,即从 一个整点可以顶到与自己相隔在k之内的数轴上的整点上。现在WYF的头变多了,于是他能顶到更远的地方,他能顶到任意整点上。现在他在玩一个游戏,这个游 戏里他只能向正方向顶,同时如果他从i顶到j,他将得到 a [ j ] ∗ ( j − i ) a[j] * (j - i) a[j](ji)的分数,其中 a [ j ] a[j] a[j] j j j点上的分数,且要求 j &gt; i j &gt; i j>i, 他最后必须停在n上。
现给出 1 → n 1\to n 1n上的所有分数,原点没有分数。他现在在原点,没有分。WYF想知道他最多能得多少分。

Input

第一行一个整数n。
第二行有n个整数,其中第i个数表示a[j]。

Output

一个整数,表示WYF最多能得到的分数。

Data Constraint

对于 60 % 60\% 60%的数据, n ≤ 1000 n\leq1000 n1000
对于 100 % 100\% 100%的数据, n ≤ 100000 , 0 ≤ a [ j ] ≤ 50 n\leq100000,0\leq a[j]\leq50 n100000,0a[j]50

Solution

f [ i ] = f [ j ] + ( i − j ) ∗ a [ i ] ; f [ k ] + ( i − k ) ∗ a [ i ] &gt; f [ k 2 ] + ( i − k 2 ) ∗ a [ i ] ; f [ k ] + i ∗ a [ i ] − k ∗ a [ i ] &gt; f [ k 2 ] + i ∗ a [ i ] − k 2 ∗ a [ i ] ; f [ k ] − k ∗ a [ i ] &gt; f [ k 2 ] − k 2 ∗ a [ i ] ; f [ k ] − f [ k 2 ] &gt; k ∗ a [ i ] − k 2 ∗ a [ i ] ; f [ k ] − f [ k 2 ] &gt; a [ i ] ∗ ( k − k 2 ) ; ( f [ k ] − f [ k 2 ] ) / ( k − k 2 ) &lt; a [ i ] ; \begin{aligned} f[i]&amp;=f[j]+(i-j)*a[i];\\[2ex] f[k]+(i-k)*a[i]&amp;&gt;f[k2]+(i-k2)*a[i];\\[2ex] f[k]+i*a[i]-k*a[i]&amp;&gt;f[k2]+i*a[i]-k2*a[i];\\[2ex] f[k]-k*a[i]&amp;&gt;f[k2]-k2*a[i];\\[2ex] f[k]-f[k2]&amp;&gt;k*a[i]-k2*a[i];\\[2ex] f[k]-f[k2]&amp;&gt;a[i]*(k-k2);\\[2ex] (f[k]-f[k2])/(k-k2)&amp;&lt;a[i]; \end{aligned} f[i]f[k]+(ik)a[i]f[k]+ia[i]ka[i]f[k]ka[i]f[k]f[k2]f[k]f[k2](f[k]f[k2])/(kk2)=f[j]+(ij)a[i];>f[k2]+(ik2)a[i];>f[k2]+ia[i]k2a[i];>f[k2]k2a[i];>ka[i]k2a[i];>a[i](kk2);<a[i];
显然是标准的斜率优化,但因为 a [ i ] a[i] a[i]不一定,斜率优化不符合单调性
以前被我们扔掉过的东西以后是可能还会有用的,所以我们此时就不能仅仅只用队列,因为如果每次返回去捡起的话,复杂度无法承担
那么我们考虑通过二分来解决这一类的问题,我们去队列中寻找 ( f [ k ] − f [ k 2 ] ) / ( k − k 2 ) &lt; a [ i ] (f[k]-f[k2])/(k-k2)&lt;a[i] (f[k]f[k2])/(kk2)<a[i]这个式子的上界,这其实就相当于我们跑了一次单调的斜率优化,前面被扔掉了而已
注意我们维护的是一个单调不上升序列

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
int a[100005],n,q[100005],f[100005],head=1,tail=1;
double g(int k,int k2)
{
	return double(f[k] - f[k2]) / (k - k2);
}
int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","r",stdout);
	scanf("%d",&n);
	for(int i = 1;i <= n;i++) 
		scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	{
		int l=head,r=tail;
		while(l<r)
		{
			int mid=(l+r)>>1;
			if(g(q[mid],q[mid+1])>a[i]) l=mid+1;
			else r=mid;
		}
		f[i]=f[q[l]]+(i-q[l])*a[i];
		while(head<tail&&g(q[tail-1],q[tail])<g(q[tail],i)) tail--;
		q[++tail]=i;
	}
	printf("%d",f[n]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值