4516 Problem A 家庭作业
思路
设置一个无法完成的时间期限 t i m e time time(截止时间在此之前的作业都无法完成,即 1 − t i m e 1-time 1−time都已被使用),设置 v i s vis vis数组,标记第 i i i天是否已使用,将作业按照学分从大到小排列,按顺序遍历,若 t > t i m e t>time t>time,对于每项作业从 t t t到1枚举天数,若能找到未被标记的一天,标记它并将该作业学分加入 s u m sum sum,若不能( 1 − t 1-t 1−t都已被使用),更新 t i m e = t time=t time=t,最后输出 s u m sum sum.
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct test{
int t,s;
}a[1000005];
bool vis[700005];
bool cmp(test a,test b)
{
return a.s>b.s ;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;++i)
{
cin>>a[i].t>>a[i].s;
}
sort(a,a+n,cmp);int ti=0,sum=0;
for(int i=0;i<n;++i)
{
if(a[i].t<=ti) continue;
int flag=0;
for(int j=a[i].t;j>=1;--j)
{
if(vis[j]==false)
{
vis[j]=true;
sum+=a[i].s;
flag=1;break;
}
}
if(flag==0) ti=a[i].t;
}
cout<<sum;
}
另一个思路:用并查集查找
将每一天的
f
f
f指向自己(未被使用),若已被使用则将
f
f
f指向前一个未被使用的日子。同样将作业按照学分从大到小排列,按顺序遍历,从
t
t
tk开始找父亲,若找到
x
=
f
[
x
]
x=f[x]
x=f[x],更新
f
[
x
]
f[x]
f[x],标记一下返回,若
x
<
=
0
x<=0
x<=0,无标记返回。最后输出
s
u
m
sum
sum.
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct test{
int t,s;
}a[1000005];
int f[700005],flag;
bool cmp(test a,test b)
{
return a.s>b.s ;
}
int find(int x)
{
if(x<=0) return x;
else if(x==f[x])
{
f[x]=x-1;
flag=1;
return f[x];
}else
return f[x]=find(f[x]);
}
int main()
{
for(int i=0;i<=7000005;++i)
f[i]=i;
int n,sum=0;cin>>n;
for(int i=0;i<n;++i)
cin>>a[i].t>>a[i].s;
sort(a,a+n,cmp);
for(int i=0;i<n;++i)
{
flag=0;
f[a[i].t]=find(a[i].t);
if(flag) sum+=a[i].s;
}
cout<<sum;
}
4518 Problem B 糖果传递
思路
输入数据同时对小朋友的糖果数量求和,可以计算出每个小朋友最后得到
a
v
e
ave
ave。
假设第
i
i
i个的小朋友有
a
i
ai
ai颗糖果,第
i
i
i个小朋友给了第
i
−
1
i-1
i−1个小朋友
x
i
xi
xi颗糖果,如果
x
i
<
0
xi<0
xi<0,说明第
i
−
1
i-1
i−1个小朋友给了第
i
i
i个小朋友
x
i
xi
xi颗糖果,
x
1
x1
x1表示第一个小朋友给第
n
n
n个小朋友的糖果数量。 所以最后的答案就是
r
e
s
=
∣
x
1
∣
+
∣
x
2
∣
+
∣
x
3
∣
+
C
+
∣
x
n
∣
res=|x1| + |x2| + |x3| + C+ |xn|
res=∣x1∣+∣x2∣+∣x3∣+C+∣xn∣。
对于第1个小朋友,他给了第
n
n
n个小朋友
x
1
x1
x1颗糖果,还剩
a
1
−
x
1
a1-x1
a1−x1颗糖果;第2个小朋友又给了他
x
2
x2
x2颗糖果,所以最后第1个小朋友还剩
a
1
−
x
1
+
x
2
a1-x1+x2
a1−x1+x2颗糖果。
所以得
a
1
−
x
1
+
x
2
=
a
v
e
a1-x1+x2=ave
a1−x1+x2=ave。
同理,可得
n
n
n个方程,变形得
第1个小朋友:
x
2
=
a
v
e
−
a
1
+
x
1
=
x
1
−
c
1
x2=ave-a1+x1 = x1-c1
x2=ave−a1+x1=x1−c1(假设
c
1
=
a
1
−
a
v
e
c1=a1-ave
c1=a1−ave,下面类似)
第2个小朋友:
x
3
=
a
v
e
−
a
2
+
x
2
=
2
a
v
e
−
a
1
−
a
2
+
x
1
=
x
1
−
c
2
x3=ave-a2+x2=2ave-a1-a2+x1=x1-c2
x3=ave−a2+x2=2ave−a1−a2+x1=x1−c2
第3个小朋友:
x
4
=
a
v
e
−
a
3
+
x
3
=
3
a
v
e
−
a
1
−
a
2
−
a
3
+
x
1
=
x
1
−
c
3
x4=ave-a3+x3=3ave-a1-a2-a3+x1=x1-c3
x4=ave−a3+x3=3ave−a1−a2−a3+x1=x1−c3
……
第n个小朋友,
a
n
−
x
n
+
x
1
=
a
v
e
an-xn+x1=ave
an−xn+x1=ave。
若让
x
i
xi
xi的绝对值之和尽量小,即
∣
x
1
∣
+
∣
x
1
−
x
1
∣
+
∣
x
1
−
c
2
∣
+
…
…
+
∣
x
1
−
c
n
−
1
∣
|x1| + |x1-x1| + |x1-c2| + ……+ |x1-cn-1|
∣x1∣+∣x1−x1∣+∣x1−c2∣+……+∣x1−cn−1∣要尽量小。注意到
∣
x
1
−
c
i
∣
|x1-ci|
∣x1−ci∣的几何意义是数轴上的点
x
1
x1
x1到
c
i
ci
ci的距离,即给定数轴上的n个点,找出一个到他们的距离之和尽量小的点,而这个点就是这些数中的中位数。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[1000005],c[1000005];
int main()
{
ll n;scanf("%lld",&n);
ll ave=0,res=0;
for(int i=1;i<=n;++i)
{
scanf("%lld",&a[i]);ave+=a[i];
}ave=ave/n;
for(int i=1;i<n;++i)
{
c[i]=c[i-1]+a[i]-ave;
}
sort(c,c+n);
ll mid;
if(n%2)
mid=c[n/2];
else mid=(c[(n-1)/2]+c[(n+1)/2])/2;
for(int i=0;i<n;++i)
res+=abs(c[i]-mid);
printf("%lld",res);
}
4696 Problem C 骑士
连接每个骑士与其厌恶的骑士,得到一个图。对于图中的每个联通块,设其节点数为
k
k
k,则它的边数一定
≤
k
≤k
≤k(可能会有重边,所以边数可能不到
k
k
k),这就意味着每个联通块不是一棵树就是一棵树上任意连一条边。
对于每个联通块:
①如果它是一棵树,考虑树形
d
p
dp
dp.
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示以
i
i
i为根的子树中,
i
i
i计不计入答案(计入⇒
j
=
1
j=1
j=1,不计入⇒
j
=
0
j=0
j=0)的最大战斗力,转移方程为
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]
=
=
=
∑
j
∈
s
o
n
i
m
a
x
\sum_{j∈soni} max
∑j∈sonimax{
d
p
[
j
]
[
0
]
,
d
p
[
j
]
[
1
]
dp[j][0],dp[j][1]
dp[j][0],dp[j][1]}
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1]
=
=
=
f
i
g
h
t
i
+
fighti+
fighti+
∑
j
∈
s
o
n
i
\sum_{j∈soni}
∑j∈soni
d
p
[
j
]
[
0
]
dp[j][0]
dp[j][0]
答案为
m
a
x
d
p
[
r
o
o
t
]
[
0
]
,
d
p
[
r
o
o
t
]
[
1
]
max{dp[root][0],dp[root][1]}
maxdp[root][0],dp[root][1]
②如果它是一棵树加一条边,我们可以先找到联通块上的唯一的环,任意删去其中的一条边
u
,
v
u,v
u,v,就得到了一棵树,鉴于
u
,
v
u,v
u,v间本应有边,所以我们考虑不取
u
u
u或不取
v
v
v,最后取
m
a
x
max
max就能知道联通块的答案,即分别以
u
,
v
u,v
u,v为根做一遍
d
p
dp
dp,最后最大战斗力为
m
a
x
max
max{
d
p
[
u
]
[
0
]
,
d
p
[
v
]
[
0
]
dp[u][0],dp[v][0]
dp[u][0],dp[v][0]}.
最终答案即为每个联通块的答案之和。
PS:注意输入时去掉重边.
参考博客,详见【BZOJ1040】【ZJOI2008】骑士 题解
学习链接
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<map>
using namespace std;
vector<int>G[1000010];
int f[1000010];
bool vis[1000010];
int hte[1000010];
long long dp[1000010][2];
int n;
int u,v;
int dep[1000010];
long long ans;
void dfs(int x,int p)
{
vis[x]=true;
for(int i=0;i<G[x].size();i++)
{
int y=G[x][i];
if(y==p)continue;
if(vis[y])
{
if(dep[y]<dep[x])u=x,v=y;
continue;
}
dep[y]=dep[x]+1;
dfs(y,x);
}
}
void solve(int x,int p)
{
dp[x][1]=f[x];
dp[x][0]=0;
for(int i=0;i<G[x].size();i++)
{
int y=G[x][i];
if((x==u && y==v) || (x==v && y==u) || y==p)continue;
solve(y,x);
dp[x][0]+=max(dp[y][0],dp[y][1]);
dp[x][1]+=dp[y][0];
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d%d",f+i,hte+i);
if(hte[hte[i]]==i)continue;
G[hte[i]].push_back(i);
G[i].push_back(hte[i]);
}
for(int i=1;i<=n;i++)
{
if(vis[i])continue;
u=v=0;
dfs(i,0);
long long res;
if(!u && !v)
{
solve(i,0);
res=max(dp[i][0],dp[i][1]);
}
else
{
solve(u,0);
res=dp[u][0];
solve(v,0);
res=max(res,dp[v][0]);
}
ans+=res;
}
printf("%lld",ans);
return 0;
}
4697 Problem D 牧场的安排
思路
简单状压
d
p
dp
dp,二进制表示每块地的状态。
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示第
i
i
i行
j
j
j状态时前i行的方案数,
d
p
[
0
]
[
0
]
=
1
dp[0][0] = 1
dp[0][0]=1作为边界(什么格子都不种的方案)。状态转移:如果本行的
j
j
j状态和上一行的
k
k
k状态不冲突,那么本行
j
j
j状态的方案数就加上上一行
k
k
k状态的方案数,即
k
k
k状态可以到
j
j
j状态。
d
p
[
i
]
[
j
]
+
=
d
p
[
i
−
1
]
[
k
]
dp[i][j]+=dp[i-1][k]
dp[i][j]+=dp[i−1][k]
因为两块相邻的地不能种草,对数据进行预处理。当
i
i
i表示每行地的状态时,((
i
i
i<<1)&
i
)
=
=
0
i)==0
i)==0表示没有两块地相邻土地状态。用
v
e
c
t
o
r
vector
vector数组保存所有可能的合法状态。当
i
i
i表示这一行的状态,
j
j
j表示上一行的状态时,
i
i
i&
j
=
=
0
j==0
j==0就表示这两行没有地相邻。
当
i
i
i表示这一行的状态,
j
j
j表示这一行是否可以种草的状态时,
i
i
i&
j
=
=
i
j==i
j==i就说明i状态是可行的,假如
i
i
i&
j
!
=
i
j!=i
j!=i,那么就存在
i
i
i状态的某一列是1,但是这一块地本身是不能种植的。用数组s来保存这一行是否可以种草的状态。
最后统计
d
p
[
n
]
[
所
有
状
态
]
dp[n][所有状态]
dp[n][所有状态]的总方案数。
学习链接
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,dp[13][401],a[13][13],s[13];
const int mod=1e8;
vector<int>v;
int main()
{
int n,m;
scanf("%d%d",&m,&n);
for(int i=0;i<(1<<n);++i)
if(((i<<1)&i)==0)
v.push_back(i);
for(int i=1;i<=m;++i)
for(int j=0;j<n;++j)
scanf("%d",&a[i][j]);
for(int i=1;i<=m;++i)
for(int j=0;j<n;++j)
s[i]=(s[i]<<1)+a[i][j];
dp[0][0]=1;
for(int i=1;i<=m;++i)
{
for(int j=0;j<v.size();++j)
{
if((v[j]&s[i])==v[j])
{
for(int k=0;k<v.size();++k)
{
if((v[k]&v[j])==0)
{
dp[i][j]+=dp[i-1][k];
dp[i][j]%=mod;
}
}
}
}
}ll res=0;
for(int i=0;i<v.size();++i)
res=(res+dp[m][i])%mod;
printf("%lld",res);
}
4924 Problem E 宝藏
思路
选择合适的方案,满足已经属于同一个联通块中的两点间不会有直接相连的第二条边,同时给定两点间连边的代价,为
L
L
L*
K
K
K,找到一个连边的顺序,最终使得所有点联通同时总代价最小(
L
∗
K
L*K
L∗K为起点到当前点的所经过的节点数乘当前边长度)
对于每个点选或不选的状态,用状压解决。
设
f
[
s
]
f[s]
f[s]表示状态
s
s
s下,使得这些选择的点在同一个联通块中的最小代价,则转移方程:(从
i
i
i点转移到
j
j
j点)
f
f
f[1<<(
j
−
1
j-1
j−1)|
s
s
s]=
f
[
s
]
+
d
i
s
[
i
]
∗
g
[
i
]
[
j
]
f[s]+dis[i]*g[i][j]
f[s]+dis[i]∗g[i][j]
其中
g
[
i
]
[
j
]
g[i][j]
g[i][j]表示
i
,
j
i,j
i,j两点间道路的距离,
d
i
s
[
i
]
dis[i]
dis[i]表示距离
i
i
i根节点间的节点数.
搜索求出最优解,以每个点为根都进行一次
d
f
s
dfs
dfs+状压
d
p
dp
dp,枚举每一个点是否被选。
参考博客,详见:[NOIP2017]宝藏-动态规划,状压dp,搜索,二进制用法模板,dfs
4767 Problem F 取石子游戏 1
思路
巴什博奕(Bash Game):
若
n
<
=
k
n<=k
n<=k,必然先手胜,若
n
=
k
+
1
n=k+1
n=k+1,必然后手胜,对
n
n
n,必有
n
=
(
k
+
1
)
∗
r
+
s
n=(k+1)*r+s
n=(k+1)∗r+s,所以只要先手取走
s
s
s个,将
r
∗
(
k
+
1
)
r*(k+1)
r∗(k+1)局面留给后手,则先手可以胜,若
s
=
0
s=0
s=0,先手面对
r
∗
(
k
+
1
)
r*(k+1)
r∗(k+1)局面,则后手胜。
即
n
n
n%(
m
+
1
)
=
=
0
m+1)==0
m+1)==0. 后手胜,反之,先手胜。
学习链接
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
int n,k;cin>>n>>k;
if(n%(k+1)) cout<<'1';
else cout<<'2';
}
4771 Problem G 巧克力棒
思路:
n
i
m
nim
nim游戏
n
i
m
nim
nim游戏一个很神奇的结论:对于一个局面,当且仅当
a
[
1
]
x
o
r
a
[
2
]
x
o
r
.
.
.
x
o
r
a
[
n
]
=
0
a[1] xor a[2] xor ...xor a[n]=0
a[1]xora[2]xor...xora[n]=0时,该局面为P局面,即必败局面。
取巧克力棒:
我们取出来最长异或子序列,留给对手的是个必败局面or 他只能再拿巧克力棒,再拿巧克力棒的话局面就变成必胜了,你只需要再把局面变成
a
[
1
]
x
o
r
a
[
2
]
x
o
r
.
.
.
x
o
r
a
[
n
]
=
0
a[1] xor a[2] xor ...xor a[n]=0
a[1]xora[2]xor...xora[n]=0.
代码
#include<bits/stdc++.h>
using namespace std;
int a[2002],panduan=0,n;
int dfs(int deep,int chang,int juge)
{
if(deep==n+1)
{
if(chang>0&&!juge)
panduan=1;
return 0;
}
dfs(deep+1,chang,juge);
dfs(deep+1,chang+1,juge^a[deep]);
}
int main()
{
int b,c,d,e,f,g,h,i,j,k,l,m;
for(i=1;i<=10;i++)
{cin>>n;panduan=0;
memset(a,0,sizeof(a));
for(b=1;b<=n;b++)
cin>>a[b];
dfs(1,0,0);
if(panduan)cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
return 0;
}