6.28集训--集训模拟赛2
总结
第一题:n
只有4
,直接暴力
第二题:Tarjan缩点之后跑一个最长路
第三题:DP
第四题:思维量较大
A、翻转游戏
题目描述
分析
\(n\)的范围很小,所以我们考虑状压DP
我们设\(f[i][j][k][m]\)为第一行的状态为\(i\),第二行的状态为\(j\),第三行的状态为\(k\),第四题的状态为\(m\)所需要的最小步数
所以我们暴力枚举6重循环,时间复杂度为\(15 \times 15 \times 15 \times 15 \times 4 \times 4\)
考场95分代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5;
int f[1<<maxn][1<<maxn][1<<maxn][1<<maxn];
int zt[maxn];
int main(){
memset(f,0x3f,sizeof(f));
for(int i=1;i<=4;i++){
char s[10];
scanf("%s",s);
for(int j=0;j<4;j++){
if(s[j]=='b'){
zt[i]+=(1<<j);
}
}
}
f[zt[1]][zt[2]][zt[3]][zt[4]]=0;
for(int i=0;i<(1<<4);i++){
for(int j=0;j<(1<<4);j++){
for(int k=0;k<(1<<4);k++){
for(int m=0;m<(1<<4);m++){
for(int d=1;d<=4;d++){
if(d==1){
for(int n=1;n<=4;n++){
if(n==1){
int now=i^(1<<(n-1));
now=now^(1<<n);
f[now][j^(1<<(n-1))][k][m]=min(f[now][j^(1<<(n-1))][k][m],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=i^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[now][j^(1<<(n-1))][k][m]=min(f[now][j^(1<<(n-1))][k][m],f[i][j][k][m]+1);
} else {
int now=i^(1<<(n-1));
now=now^(1<<(n-2));
f[now][j^(1<<(n-1))][k][m]=min(f[now][j^(1<<(n-1))][k][m],f[i][j][k][m]+1);
}
}
} else if(d==2){
for(int n=1;n<=4;n++){
if(n==1){
int now=j^(1<<(n-1));
now=now^(1<<n);
f[i^(1<<(n-1))][now][k^(1<<(n-1))][m]=min(f[i^(1<<(n-1))][now][k^(1<<(n-1))][m],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=j^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[i^(1<<(n-1))][now][k^(1<<(n-1))][m]=min(f[i^(1<<(n-1))][now][k^(1<<(n-1))][m],f[i][j][k][m]+1);
} else {
int now=j^(1<<(n-1));
now=now^(1<<(n-2));
f[i^(1<<(n-1))][now][k^(1<<(n-1))][m]=min(f[i^(1<<(n-1))][now][k^(1<<(n-1))][m],f[i][j][k][m]+1);
}
}
} else if(d==3){
for(int n=1;n<=4;n++){
if(n==1){
int now=k^(1<<(n-1));
now=now^(1<<n);
f[i][j^(1<<(n-1))][now][m^(1<<(n-1))]=min(f[i][j^(1<<(n-1))][now][m^(1<<(n-1))],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=k^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[i][j^(1<<(n-1))][now][m^(1<<(n-1))]=min(f[i][j^(1<<(n-1))][now][m^(1<<(n-1))],f[i][j][k][m]+1);
} else {
int now=k^(1<<(n-1));
now=now^(1<<(n-2));
f[i][j^(1<<(n-1))][now][m^(1<<(n-1))]=min(f[i][j^(1<<(n-1))][now][m^(1<<(n-1))],f[i][j][k][m]+1);
}
}
} else {
for(int n=1;n<=4;n++){
if(n==1){
int now=m^(1<<(n-1));
now=now^(1<<n);
f[i][j][k^(1<<(n-1))][now]=min(f[i][j][k^(1<<(n-1))][now],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=m^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[i][j][k^(1<<(n-1))][now]=min(f[i][j][k^(1<<(n-1))][now],f[i][j][k][m]+1);
} else {
int now=m^(1<<(n-1));
now=now^(1<<(n-2));
f[i][j][k^(1<<(n-1))][now]=min(f[i][j][k^(1<<(n-1))][now],f[i][j][k][m]+1);
}
}
}
}
}
}
}
}
int ans=min(f[0][0][0][0],f[15][15][15][15]);
if(ans==0x3f3f3f3f) printf("Impossible\n");
else printf("%d\n",ans);
return 0;
}
100分代码
因为我们的状态不是从0
开始的,所以我们不能只从状态为0
正着开始枚举
比如下面这一组数据
bbbb
bwwb
bwwb
bbbb
正解应该输出4,但是上面的代码却输出6
解决这一个问题,我们倒着枚举一遍就可以了
#include<bits/stdc++.h>
using namespace std;
const int maxn=5;
int f[1<<maxn][1<<maxn][1<<maxn][1<<maxn];
int zt[maxn];
int main(){
memset(f,0x3f,sizeof(f));
for(int i=1;i<=4;i++){
char s[10];
scanf("%s",s);
for(int j=0;j<4;j++){
if(s[j]=='b'){
zt[i]+=(1<<j);
}
}
}
f[zt[1]][zt[2]][zt[3]][zt[4]]=0;
for(int i=0;i<(1<<4);i++){
for(int j=0;j<(1<<4);j++){
for(int k=0;k<(1<<4);k++){
for(int m=0;m<(1<<4);m++){
for(int d=1;d<=4;d++){
if(d==1){
for(int n=1;n<=4;n++){
if(n==1){
int now=i^(1<<(n-1));
now=now^(1<<n);
f[now][j^(1<<(n-1))][k][m]=min(f[now][j^(1<<(n-1))][k][m],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=i^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[now][j^(1<<(n-1))][k][m]=min(f[now][j^(1<<(n-1))][k][m],f[i][j][k][m]+1);
} else {
int now=i^(1<<(n-1));
now=now^(1<<(n-2));
f[now][j^(1<<(n-1))][k][m]=min(f[now][j^(1<<(n-1))][k][m],f[i][j][k][m]+1);
}
}
} else if(d==2){
for(int n=1;n<=4;n++){
if(n==1){
int now=j^(1<<(n-1));
now=now^(1<<n);
f[i^(1<<(n-1))][now][k^(1<<(n-1))][m]=min(f[i^(1<<(n-1))][now][k^(1<<(n-1))][m],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=j^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[i^(1<<(n-1))][now][k^(1<<(n-1))][m]=min(f[i^(1<<(n-1))][now][k^(1<<(n-1))][m],f[i][j][k][m]+1);
} else {
int now=j^(1<<(n-1));
now=now^(1<<(n-2));
f[i^(1<<(n-1))][now][k^(1<<(n-1))][m]=min(f[i^(1<<(n-1))][now][k^(1<<(n-1))][m],f[i][j][k][m]+1);
}
}
} else if(d==3){
for(int n=1;n<=4;n++){
if(n==1){
int now=k^(1<<(n-1));
now=now^(1<<n);
f[i][j^(1<<(n-1))][now][m^(1<<(n-1))]=min(f[i][j^(1<<(n-1))][now][m^(1<<(n-1))],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=k^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[i][j^(1<<(n-1))][now][m^(1<<(n-1))]=min(f[i][j^(1<<(n-1))][now][m^(1<<(n-1))],f[i][j][k][m]+1);
} else {
int now=k^(1<<(n-1));
now=now^(1<<(n-2));
f[i][j^(1<<(n-1))][now][m^(1<<(n-1))]=min(f[i][j^(1<<(n-1))][now][m^(1<<(n-1))],f[i][j][k][m]+1);
}
}
} else {
for(int n=1;n<=4;n++){
if(n==1){
int now=m^(1<<(n-1));
now=now^(1<<n);
f[i][j][k^(1<<(n-1))][now]=min(f[i][j][k^(1<<(n-1))][now],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=m^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[i][j][k^(1<<(n-1))][now]=min(f[i][j][k^(1<<(n-1))][now],f[i][j][k][m]+1);
} else {
int now=m^(1<<(n-1));
now=now^(1<<(n-2));
f[i][j][k^(1<<(n-1))][now]=min(f[i][j][k^(1<<(n-1))][now],f[i][j][k][m]+1);
}
}
}
}
}
}
}
}
for(int i=(1<<4)-1;i>=0;i--){
for(int j=(1<<4)-1;j>=0;j--){
for(int k=(1<<4)-1;k>=0;k--){
for(int m=(1<<4)-1;m>=0;m--){
for(int d=1;d<=4;d++){
if(d==1){
for(int n=1;n<=4;n++){
if(n==1){
int now=i^(1<<(n-1));
now=now^(1<<n);
f[now][j^(1<<(n-1))][k][m]=min(f[now][j^(1<<(n-1))][k][m],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=i^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[now][j^(1<<(n-1))][k][m]=min(f[now][j^(1<<(n-1))][k][m],f[i][j][k][m]+1);
} else {
int now=i^(1<<(n-1));
now=now^(1<<(n-2));
f[now][j^(1<<(n-1))][k][m]=min(f[now][j^(1<<(n-1))][k][m],f[i][j][k][m]+1);
}
}
} else if(d==2){
for(int n=1;n<=4;n++){
if(n==1){
int now=j^(1<<(n-1));
now=now^(1<<n);
f[i^(1<<(n-1))][now][k^(1<<(n-1))][m]=min(f[i^(1<<(n-1))][now][k^(1<<(n-1))][m],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=j^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[i^(1<<(n-1))][now][k^(1<<(n-1))][m]=min(f[i^(1<<(n-1))][now][k^(1<<(n-1))][m],f[i][j][k][m]+1);
} else {
int now=j^(1<<(n-1));
now=now^(1<<(n-2));
f[i^(1<<(n-1))][now][k^(1<<(n-1))][m]=min(f[i^(1<<(n-1))][now][k^(1<<(n-1))][m],f[i][j][k][m]+1);
}
}
} else if(d==3){
for(int n=1;n<=4;n++){
if(n==1){
int now=k^(1<<(n-1));
now=now^(1<<n);
f[i][j^(1<<(n-1))][now][m^(1<<(n-1))]=min(f[i][j^(1<<(n-1))][now][m^(1<<(n-1))],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=k^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[i][j^(1<<(n-1))][now][m^(1<<(n-1))]=min(f[i][j^(1<<(n-1))][now][m^(1<<(n-1))],f[i][j][k][m]+1);
} else {
int now=k^(1<<(n-1));
now=now^(1<<(n-2));
f[i][j^(1<<(n-1))][now][m^(1<<(n-1))]=min(f[i][j^(1<<(n-1))][now][m^(1<<(n-1))],f[i][j][k][m]+1);
}
}
} else {
for(int n=1;n<=4;n++){
if(n==1){
int now=m^(1<<(n-1));
now=now^(1<<n);
f[i][j][k^(1<<(n-1))][now]=min(f[i][j][k^(1<<(n-1))][now],f[i][j][k][m]+1);
} else if(n==2 || n==3){
int now=m^(1<<(n-1));
now=now^(1<<n);
now=now^(1<<(n-2));
f[i][j][k^(1<<(n-1))][now]=min(f[i][j][k^(1<<(n-1))][now],f[i][j][k][m]+1);
} else {
int now=m^(1<<(n-1));
now=now^(1<<(n-2));
f[i][j][k^(1<<(n-1))][now]=min(f[i][j][k^(1<<(n-1))][now],f[i][j][k][m]+1);
}
}
}
}
}
}
}
}
int ans=min(f[0][0][0][0],f[15][15][15][15]);
if(ans==0x3f3f3f3f) printf("Impossible\n");
else printf("%d\n",ans);
return 0;
}
B、抢掠计划
题目描述
分析
显然,处于同一个强连通分量的点可以互相到达
所以我们将每一个强连通分量分别缩点
缩完点后的图就变成了一个有向无环图
这时,我们就可以用SPFA求出最长路
100分代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e6+5;
int head[maxn],tot=1;
struct asd{
int from,to,next;
ll val;
}b[maxn],b2[maxn];
void ad(int aa,int bb,ll cc){
b[tot].from=aa;
b[tot].to=bb;
b[tot].next=head[aa];
b[tot].val=cc;
head[aa]=tot++;
}
int h2[maxn],t2=1;
void ad2(int aa,int bb){
b2[t2].from=aa;
b2[t2].to=bb;
b2[t2].next=h2[aa];
h2[aa]=t2++;
}
int dfn[maxn],low[maxn],dfnc,sta[maxn],top;
int js,shuyu[maxn],siz[maxn];
ll a[maxn],ans[maxn];
void tar(int xx){
dfn[xx]=low[xx]=++dfnc;
sta[++top]=xx;
for(int i=h2[xx];i!=-1;i=b2[i].next){
int u=b2[i].to;
if(!dfn[u]){
tar(u);
low[xx]=min(low[xx],low[u]);
} else if(!shuyu[u]){
low[xx]=min(low[xx],dfn[u]);
}
}
if(dfn[xx]==low[xx]){
js++;
while(sta[top]!=xx){
int y=sta[top--];
shuyu[y]=js;
siz[js]++;
}
top--;
shuyu[xx]=js;
siz[js]++;
}
}
queue<int> q;
int vis[maxn];
ll dis[maxn];
void SPFA(int xx){
q.push(xx);
vis[xx]=1;
while(!q.empty()){
int now=q.front();
q.pop();
vis[now]=0;
for(int i=head[now];i!=-1;i=b[i].next){
int u=b[i].to;
if(dis[u]<dis[now]+b[i].val){
dis[u]=dis[now]+b[i].val;
if(vis[u]==0){
q.push(u);
vis[u]=1;
}
}
}
}
}
int main(){
memset(head,-1,sizeof(head));
memset(h2,-1,sizeof(h2));
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int aa,bb;
scanf("%d%d",&aa,&bb);
ad2(aa,bb);
}
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tar(i);
}
for(int i=1;i<=n;i++){
ans[shuyu[i]]+=a[i];
}
for(int i=1;i<=n;i++){
for(int j=h2[i];j!=-1;j=b2[j].next){
int u=b2[j].to;
if(shuyu[i]!=shuyu[u]){
ad(shuyu[i],shuyu[u],ans[shuyu[u]]);
}
}
}
memset(vis,0,sizeof(vis));
for(int i=0;i<maxn;i++){
dis[i]=-0x3f3f3f3f3f3f3f3f;
}
int s,p;
scanf("%d%d",&s,&p);
dis[shuyu[s]]=ans[shuyu[s]];
SPFA(shuyu[s]);
ll mans=0;
for(int i=1;i<=p;i++){
int aa;
scanf("%d",&aa);
mans=max(mans,dis[shuyu[aa]]);
}
printf("%lld\n",mans);
return 0;
}
C、测绘
题目描述
分析
这一道题非常显然是一道DP题
我们设\(f[i][j]\)为前\(i\)个数中已经选择了\(j\)个数的价值,并且处于\(i\)位置上的数一定选择
那么就有$ f[i][j]= \min (f[i][j],f[k][j-1]+sum[k][i]);\(
其中\)j-1 \leq k < i\(
\)sum[i][j]\(为选走\)i,j\(位置上的数,区间\)[i,j]\(中的数产生的误差
这一道题要注意初始化和最后的处理
我们需要将\)f[i][1]\(和\)f[i][i]\(预处理一下
同时,当\)DP$结束后,因为最右边的价值没有计算,所以我们需要把最右边的价值加上
100分代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
int n,vis[maxn],jud;
ll e,a[maxn],sum[maxn][maxn],f[maxn][maxn];
int main(){
memset(f,0x3f,sizeof(f));
scanf("%d%lld",&n,&e);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
for(int k=i+1;k<j;k++){
sum[i][j]+=abs(a[k]*2-a[i]-a[j]);
}
}
}
for(int i=1;i<=n;i++) f[i][i]=0;
for(int i=1;i<=n;i++){
f[i][1]=0;
for(int j=1;j<i;j++){
f[i][1]+=2*abs(a[i]-a[j]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
for(int k=j-1;k<i;k++){
f[i][j]=min(f[i][j],f[k][j-1]+sum[k][i]);
}
}
}
for(int i=1;i<n;i++){
for(int j=1;j<=i;j++){
ll ans=0;
for(int k=i+1;k<=n;k++){
ans+=2*abs(a[k]-a[i]);
}
f[i][j]+=ans;
}
}
int jl=n;
ll ans=0;
for(int i=1;i<=n;i++){
ll now=0x3f3f3f3f3f3f3f3f;
for(int j=i;j<=n;j++){
now=min(now,f[j][i]);
}
if(now<=e){
ans=now;
jl=i;
break;
}
}
printf("%d %lld\n",jl,ans);
return 0;
}
D、奖学金
分析
数据范围高达 10 万,显然至少是\(O(n∗log(n))\) 才能通过。
我们分析性质中位数 \(ai\) 必须满足:\(n2+1≤i≤C−n2\) 。
当 \(i=n2+1\) 时,我们必须选上最小分数最低的前 \(n2\) 的猪。
所以我们可以枚举每一个中位数,用一个维护奖金的大根堆,每枚举完一个中位数,如果当前的奖金比堆顶的小,则交换,始终保证堆的有 \(n2\) 个数,同时用一个数组 \(f[i]\) 维护如果选 \(ai\) 为中位数,前 \(n2\) 个数的最小奖金。
同上,倒序维护,求出 \(g[i]\) 表示,如果选 \(ai\) 为中位数,则后 \(n2\) 个数最小奖金。
显然答案为满足 \(f[i]+g[i]+a[i].w<=F\) 的最大的 \(a[i].s\) 。
代码
#include <bits/stdc++.h>
const int maxn=2e5+5;
int n,c,F;
std::priority_queue <int> q;
struct Node{
int s,w;//分数,奖金
} a[maxn];
bool cmp(const Node &a, const Node &b){
return a.s<b.s;
}
int f[maxn],g[maxn],sum;;
void Init(){
scanf("%d%d%d", &n,&c,&F);
for(int i=1;i<=c;++i)
scanf("%d%d", &a[i].s,&a[i].w);
std::sort(a+1,a+1+c,cmp);//按成绩升序
}
void Solve(){
for(int i=1;i<=n/2;++i){//成绩最低的n/2进入队列
sum+=a[i].w;//累加总奖金
q.push(a[i].w);//队列是维护奖金的大根堆
}
//f[i]:表示以i为中位数前n/2人的最小奖金
for(int i=n/2+1;i<=c;++i){
f[i]=sum;
int top=q.top();
if(top>a[i].w){//如果当前的奖金小于堆顶则交换掉
q.pop();
sum-=top;
sum+=a[i].w;
q.push(a[i].w);
}
}
sum=0;
while(!q.empty()) q.pop();
for(int i=c;i>=c-n/2+1;--i){//成绩最高的n/2进入队列
sum+=a[i].w;
q.push(a[i].w);
}
//g[i]:表示以i为中位数后n/2人的最低奖金
for(int i=c-n/2;i>=1;--i){
g[i]=sum;
int top=q.top();
if(top>a[i].w){//交换
q.pop();
sum-=top;
sum+=a[i].w;
q.push(a[i].w);
}
}
//中位数的取值范围是[n/2+1,c-n/2]
//因为要求最大中位数,所以倒序
for(int i=c-n/2;i>=n/2+1;--i)
if(a[i].w+f[i]+g[i]<=F){
printf("%d", a[i].s);
return;
}
printf("-1\n");
}
int main(){
Init();
Solve();
return 0;
}