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
n≤10
对于
40
%
40\%
40%的数据:
n
≤
20
n\leq 20
n≤20
对于
100
%
100\%
100%的数据:
1
≤
n
≤
50
1\leq n\leq50
1≤n≤50,
1
≤
m
≤
500
1\leq m\leq500
1≤m≤500,
0
≤
c
≤
1000
0\leq c\leq1000
0≤c≤1000
图中可能会有重边,自环。
点的编号为
1
→
n
1\to n
1→n。
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
3≤n≤100000,3≤m≤10;
对于
100
%
100\%
100%的数据,
3
≤
n
≤
1
0
18
,
3
≤
m
≤
50
3≤n≤10^{18},3≤m≤50
3≤n≤1018,3≤m≤50。
Solution
设有 n 个点时,方案数为
A
[
n
]
A[n]
A[n]。
一个一个点考虑,加入第
n
n
n 个点时:
其左右不同,则插入之前的方案数为
A
[
n
−
1
]
A[n-1]
A[n−1],第
n
n
n 个点可以有
m
2
m2
m2 种选择。
其左右相同,在插入之前,则删掉其中一个点之后的的
n
−
2
n-2
n−2 个点
是满足要求的方案,数量为
A
[
n
−
2
]
A[n-2]
A[n−2],则
n
n
n 的颜色只需与相邻的不同,
有
m
−
1
m-1
m−1 种方案。
综上,递推公式为
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[n−1]∗(m−2)+A[n−2]∗(m−1)。
通项公式(特征根):
A
[
n
]
=
(
m
−
1
)
n
+
(
m
−
1
)
∗
(
−
1
)
n
A[n] = (m-1)^n + (m-1)*(-1)^n
A[n]=(m−1)n+(m−1)∗(−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]∗(j−i)的分数,其中
a
[
j
]
a[j]
a[j]是
j
j
j点上的分数,且要求
j
>
i
j > i
j>i, 他最后必须停在n上。
现给出
1
→
n
1\to n
1→n上的所有分数,原点没有分数。他现在在原点,没有分。WYF想知道他最多能得多少分。
Input
第一行一个整数n。
第二行有n个整数,其中第i个数表示a[j]。
Output
一个整数,表示WYF最多能得到的分数。
Data Constraint
对于
60
%
60\%
60%的数据,
n
≤
1000
n\leq1000
n≤1000;
对于
100
%
100\%
100%的数据,
n
≤
100000
,
0
≤
a
[
j
]
≤
50
n\leq100000,0\leq a[j]\leq50
n≤100000,0≤a[j]≤50。
Solution
f
[
i
]
=
f
[
j
]
+
(
i
−
j
)
∗
a
[
i
]
;
f
[
k
]
+
(
i
−
k
)
∗
a
[
i
]
>
f
[
k
2
]
+
(
i
−
k
2
)
∗
a
[
i
]
;
f
[
k
]
+
i
∗
a
[
i
]
−
k
∗
a
[
i
]
>
f
[
k
2
]
+
i
∗
a
[
i
]
−
k
2
∗
a
[
i
]
;
f
[
k
]
−
k
∗
a
[
i
]
>
f
[
k
2
]
−
k
2
∗
a
[
i
]
;
f
[
k
]
−
f
[
k
2
]
>
k
∗
a
[
i
]
−
k
2
∗
a
[
i
]
;
f
[
k
]
−
f
[
k
2
]
>
a
[
i
]
∗
(
k
−
k
2
)
;
(
f
[
k
]
−
f
[
k
2
]
)
/
(
k
−
k
2
)
<
a
[
i
]
;
\begin{aligned} f[i]&=f[j]+(i-j)*a[i];\\[2ex] f[k]+(i-k)*a[i]&>f[k2]+(i-k2)*a[i];\\[2ex] f[k]+i*a[i]-k*a[i]&>f[k2]+i*a[i]-k2*a[i];\\[2ex] f[k]-k*a[i]&>f[k2]-k2*a[i];\\[2ex] f[k]-f[k2]&>k*a[i]-k2*a[i];\\[2ex] f[k]-f[k2]&>a[i]*(k-k2);\\[2ex] (f[k]-f[k2])/(k-k2)&<a[i]; \end{aligned}
f[i]f[k]+(i−k)∗a[i]f[k]+i∗a[i]−k∗a[i]f[k]−k∗a[i]f[k]−f[k2]f[k]−f[k2](f[k]−f[k2])/(k−k2)=f[j]+(i−j)∗a[i];>f[k2]+(i−k2)∗a[i];>f[k2]+i∗a[i]−k2∗a[i];>f[k2]−k2∗a[i];>k∗a[i]−k2∗a[i];>a[i]∗(k−k2);<a[i];
显然是标准的斜率优化,但因为
a
[
i
]
a[i]
a[i]不一定,斜率优化不符合单调性
以前被我们扔掉过的东西以后是可能还会有用的,所以我们此时就不能仅仅只用队列,因为如果每次返回去捡起的话,复杂度无法承担
那么我们考虑通过二分来解决这一类的问题,我们去队列中寻找
(
f
[
k
]
−
f
[
k
2
]
)
/
(
k
−
k
2
)
<
a
[
i
]
(f[k]-f[k2])/(k-k2)<a[i]
(f[k]−f[k2])/(k−k2)<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]);
}