6.28 集训模拟赛2

T1翻转游戏

题目描述

  翻转游戏是在一个 4×4的正方形上进行的,在正方形的 个格上每个格子都放着一个双面的物件。每个物件的两个面,一面是白色,另一面是黑色,每个物件要么白色朝上,要么黑色朝上,每一次你只能翻 个物件,从而由黑到白的改变这些物件上面的颜色,反之亦然。每一轮被选择翻转的物件遵循以下规则:

  • 1.从16个物件中任选一个。
  • 2.翻转所选择的物件的同时,与它相邻的左右上下四个物件(如果有的话)都要跟着翻转。
  • bwbw
    wwww
    bbwb
    bwwb
  • 这里b表示该格子放的物件黑色朝上,w表示该格子白色朝上。如果我们选择翻转第三行第一件物件,那么格子状态将变为

    bwbw
    bwww
    wwwb
    wwwb
    
  • 游戏的目标是翻转所有的物件白色朝上或黑色朝上。你的任务就是写一个程序来求最少的的翻转次数来实现这一目标。

输入格式

  • 输入文件包含4行,每行4个字符,每个字符w或b表示游戏开始时格子上物件的状态。

输出格式

  • 输出文件仅一个整数,即从给定状态到实现这一任务的最少翻转次数。如果不能实现就输出:Impossible。如能实现就输出结果

样例

  • 输入

    bwwb
    bbwb
    bwwb
    bwww
    
  • 输出

    4
    

暴力枚举

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 char s[5];
 4 int a[18],b[18];
 5 int sum,ans=1e7;
 6 void change(int i){
 7     b[i]^=1;
 8     sum++;
 9     if(i>=5)b[i-4]^=1;
10     if(i<=12)b[i+4]^=1;
11     if(i%4!=1)b[i-1]^=1;
12     if(i%4!=0)b[i+1]^=1;
13 }
14 
15 bool j(){
16     int cnt=0;
17     for(int i=1;i<=16;i++){
18         cnt+=b[i];
19     }
20     if(cnt==0||cnt==16)return 1;
21     else return 0;
22 }
23 
24 int main(){
25     for(int i=1;i<=4;i++){
26         scanf("%s",s+1);
27         for(int j=1;j<=4;j++){
28             if(s[j]=='b')a[(i-1)*4+j]=1;
29         }
30     }
31     int maxn=1<<16;
32     for(int s=0;s<maxn;s++){
33         for(int i=1;i<=16;i++){
34             b[i]=a[i];
35         }
36         sum=0;
37         for(int i=1;i<=16;i++){
38             if(s&(1<<(i-1))){
39                 change(i);
40             }
41         }
42         if(j())ans=min(ans,sum);
43     }
44     if(ans==1e7)printf("Impossible\n");
45     else printf("%d\n",ans);
46     return 0;
47 }

T2抢掠计划

题目描述:

Siruseri 城中的道路都是单向的。不同的道路由路口连接。按照法律的规定,在每个路口都设立了一个 Siruseri 银行的 ATM 取款机。令人奇怪的是,Siruseri 的酒吧也都设在路口,虽然并不是每个路口都设有酒吧。

Banditji 计划实施 Siruseri 有史以来最惊天动地的 ATM 抢劫。他将从市中心出发,沿着单向道路行驶,抢劫所有他途径的 ATM 机,最终他将在一个酒吧庆祝他的胜利。

使用高超的黑客技术,他获知了每个 ATM 机中可以掠取的现金数额。他希望你帮助他计算从市中心出发最后到达某个酒吧时最多能抢劫的现金总数。他可以经过同一路口或道路任意多次。但只要他抢劫过某个 ATM 机后,该 ATM 机里面就不会再有钱了。 例如,假设该城中有 666 个路口,道路的连接情况如下图所示:

市中心在路口 1,由一个入口符号 → 来标识,那些有酒吧的路口用双圈来表示。每个 ATM 机中可取的钱数标在了路口的上方。在这个例子中,Banditji 能抢劫的现金总数为 47,实施的抢劫的路线为1-2-4-1-2-3-5。

输入格式

第一行包含两个整数 N,MN表示路口的个数,M表示道路条数。

接下来 M行,每行两个整数,这两个整数都在 1N之间,第 i+1行的两个整数表示第 i条道路的起点和终点的路口编号。

接下来 N行,每行一个整数,按顺序表示每个路口处的 ATM 机中的钱数 ai

接下来一行包含两个整数 S,PS表示市中心的编号,也就是出发的路口。P表示酒吧数目。

接下来的一行中有 P个整数,表示 P个有酒吧的路口的编号。

输出格式

输出一个整数,表示 Banditji 从市中心开始到某个酒吧结束所能抢劫的最多的现金总数。

输入输出样例

6 7 
1 2 
2 3 
3 5 
2 4 
4 1 
2 6 
6 5 
10 
12 
8 
16 
1 
5 
1 4 
4 3 5 6

 输出

47

说明/提示

对于50%的数据,保证N,M≤3000。

对于100%的数据保证N,M≤5×105,0≤ai≤4000。

保证可以从市中心沿着 Siruseri 的单向的道路到达其中的至少一个酒吧。

分析

首先利用tarjan缩点,将点权当作边权再建有权边,跑一边spfa最长路(板子很重要,考试的时候都忘了)

N,M
  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 const int N=5e5+10;
  4 int n,m,cnt,top;
  5 int u[N],v[N],val[N];
  6 int bar[N],Time;
  7 int sta[N];
  8 bool vis[N];
  9 int dis[N];
 10 int s,p,head[N];
 11 int dfn[N],low[N];
 12 int tot,sum[N],g[N];
 13 queue<int>q;
 14 struct edge{//两次建边结构体
 15     int to;
 16     int ne;
 17     int w;
 18 }e[N];
 19 
 20 void clear(){//清空函数
 21     cnt=0;
 22     memset(e,0,sizeof(e));//前楼大佬说结构体也能memset,但得保证类型都一样啊!!
 23     memset(head,0,sizeof(head));
 24 }
 25 
 26 void add(int u,int v){//第一次建边
 27     e[++cnt].to=v;
 28     e[cnt].ne=head[u];
 29     head[u]=cnt;
 30 }
 31 
 32 void build(int u,int v,int w){//第二次建边
 33     e[++cnt].to=v;
 34     e[cnt].ne=head[u];
 35     e[cnt].w=w;
 36     head[u]=cnt;
 37 }
 38 
 39 void read(){//读入
 40     scanf("%d%d",&n,&m);
 41     for(int i=1;i<=m;i++){
 42         scanf("%d%d",&u[i],&v[i]);
 43         add(u[i],v[i]);
 44     }
 45     for(int i=1;i<=n;i++){
 46         scanf("%d",&val[i]);
 47     }
 48     scanf("%d%d",&s,&p);
 49     for(int i=1;i<=p;i++){
 50         scanf("%d",&bar[i]);
 51     }
 52 }
 53 
 54 void tarjan(int u){//tarjan板子(板子狠重要呀)
 55     dfn[u]=low[u]=++Time;
 56     vis[u]=1;
 57     sta[++top]=u;
 58     for(int i=head[u];i;i=e[i].ne){
 59         int next=e[i].to;
 60         if(!dfn[next]){
 61             tarjan(next);
 62             low[u]=min(low[u],low[next]);
 63         }
 64         if(vis[next]){
 65             low[u]=min(low[u],dfn[next]);
 66         }
 67     }
 68     if(dfn[u]==low[u]){//缩点
 69         tot++;
 70         while(sta[top+1]!=u){
 71             int tp=sta[top--];
 72             sum[tot]+=val[tp];
 73             vis[tp]=0;
 74             g[tp]=tot;
 75         }
 76         //top--;
 77     }
 78 }
 79 
 80 void spfa(int s){//spfa最长路
 81     for(int i=1;i<=tot;i++)dis[i]=0;
 82     int gs=g[s];
 83     q.push(gs);
 84     vis[gs]=1;
 85     dis[gs]=sum[gs];
 86     while(!q.empty()){
 87         int f=q.front();
 88         q.pop();
 89         vis[f]=0;
 90         for(int i=head[f];i;i=e[i].ne){
 91             int next=e[i].to;
 92             if(dis[next]<dis[f]+e[i].w){//符号改成>就是最短路了呀
 93                 dis[next]=dis[f]+e[i].w;
 94                 if(!vis[next]){
 95                     q.push(next);
 96                     vis[next]=1;
 97                 }
 98             }
 99         }
100     }
101 }
102 
103 int main(){
104     read();
105     for(int i=1;i<=n;i++){
106         if(!dfn[i])tarjan(i);
107     }
108     clear();
109     for(int i=1;i<=m;i++){
110         if(g[u[i]]!=g[v[i]]){
111             build(g[u[i]],g[v[i]],sum[g[v[i]]]);
112         }
113     }
114     spfa(s);
115     int ans=-10;
116     for(int i=1;i<=p;i++){
117         ans=max(ans,dis[g[bar[i]]]);//枚举酒吧节点
118     }
119     printf("%d",ans);
120     return 0;
121 }
//完结撒花

T3测绘

T4奖学金

题目背景

小张最近发表了一篇论文,有一个神秘人物要给小张学院发奖学金。

题目描述

小张学院有 c名学生,第 iii 名学生的成绩为 ai,要获得的奖学金金额为 bi
要从这c名学生中挑出n名学生发奖学金。这个神秘人物爱好奇特,他希望得到奖学金的同学的成绩中位数尽可能大,但同时,他们的奖学金总额不能超过 f

输入格式

第一行有三个整数,分别表示要挑出的学生人数n,学生总数c和奖学金总额的最大值f.

第二到(c+1)行,每行两个整数,第(i+1)行的整数依次表示第i名学生的成绩ai和要给他发的奖学金金额数bi

输出格式

输出一行一个整数表示答案。如果无法满足神秘人的条件,请输出 −1

输入输出样例

3 5 70
30 25
50 21
20 20
5 18
35 30

 输出

35
  • 对于30%的数据,保证n≤103,c≤2×103
  • 对于100%的数据,保证3≤n≤105,n≤c≤2×105,0≤f≤2×109,0≤ai≤2×109,0≤bi≤105

 分析:

  • 数据范围如此之大暴力枚举一定是不能AC的,那么我们就用优化:大根堆(用优先队列默认大根堆,那就转载一篇博文https://www.cnblogs.com/huashanqingzhu/p/11040390.html)
  • 我们分析中位数ai必须满足:n⁄2+1≤i≤c-n⁄2。
  • 当 i = n/2+1时,我们必须悬赏最小分数最低的前n/2的人。
  • 所以我们可一枚举每一个中位数,用一个维护奖金的的大根堆,没枚举完一个中位数,如果当前的奖金比堆顶小则交换,始终保持堆有n/2个数,同时用一个数组f [ i ]维护ai为中位数,前n/2个数的最小奖金。
  • 同上到序维护,求出g [ i ]表示如果选ai为中位数后n/2个数的最小奖金。
  • 显然答案为满足f [ i ] + g [ i ] + e [ i ].m ≤ f 的最大e [ i ].cj。
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int N=2e5+10; 
 5 ll F;
 6 int n,c;
 7 ll f[N],g[N];
 8 struct Node{
 9     ll cj;//成绩
10     ll m;//奖金
11 }e[N];
12 
13 bool cmp(Node a,Node b){
14     return a.cj<b.cj;//按找成绩排序
15 }
16 ll sum;
17 priority_queue<int>q;//大根堆
18 
19 int main(){
20     scanf("%d%d%lld",&n,&c,&F);
21     for(int i=1;i<=c;i++){
22         scanf("%lld%lld",&e[i].cj,&e[i].m);
23     }
24     sort(e+1,e+1+c,cmp);
25     for(int i=1;i<=n/2;i++){//成绩前n/2的人入队
26         sum+=e[i].m;//sum记录前二分之i个里的奖金数。大根堆放入。
27         q.push(e[i].m);
28     }
29     for(int i=n/2+1;i<=c;i++){//f[i]:表示以i为中位数前n/2人的最小奖金
30         f[i]=sum;
31         ll top = q.top();
32         if(top>e[i].m){//奖金小于堆顶则换掉,因为越小越好。
33             q.pop();
34             sum-=top;
35             sum+=e[i].m;
36             q.push(e[i].m);
37         }
38     }
39     sum=0;
40     while(!q.empty())q.pop();
41     for(int i=c;i>=c-(n/2)+1;i--){//成绩后n/2入队
42         sum+=e[i].m;
43         q.push(e[i].m);
44     }
45     for(int i=c-(n/2);i>=1;i--){//g[i]是i为中位数是后n/2人最小奖金
46         g[i]=sum;
47         ll top = q.top();
48         if(top>e[i].m){//同上,如果小,就换
49             q.pop();
50             sum-=top;
51             sum+=e[i].m;
52             q.push(e[i].m);
53         }
54     }
55     for(int i=c-n/2;i>=n/2+1;i--){//枚举每一种中位数的可能
56         if(f[i]+g[i]+e[i].m<=F){//因为是从大到小枚举的,所以满足情况就直接输出结束。
57             printf("%lld",e[i].cj);
58             return 0;
59         }
60     }
61     printf("-1\n");//没有合法情况就输出-1
62     return 0;
63 }

猜你喜欢

转载自www.cnblogs.com/LightyaChoo/p/13204285.html