集訓小模擬賽2
A.翻轉游戲
題目描述
翻轉游戲是在一個\(4*4\)的正方形上進行的,在正方形的\(16\)個格上每個格子都放著一個雙面的物件。每個物件的兩個面,一面是白色,另一面是黑色,每個物件要麼白色朝上,要麼黑色朝上,每一次你只能翻\(1\)個物件,從而由黑到白的改變這些物件上面的顏色,反之亦然。每一輪被選擇翻轉的物件遵循以下規則:
從\(16\)個物件中任選一個。
翻轉所選擇的物件的同時,所有與它相鄰的左方物件、右方物件、上方物件和下方物件(如果有的話),都要跟著翻轉。
以下為例:
bwbw
wwww
bbwb
bwwb
這裡b表示該格子放的物件黑色面朝上、w表示該格子放的物件白色朝上。如果我們選擇翻轉第三行的第一個物件,那麼格子狀態將變為:
bwbw
bwww
wwwb
wwwb
遊戲的目標是翻轉到所有的物件白色朝上或黑色朝上。你的任務就是寫一個程式來求最少的翻轉次數來實現這一目標。
輸入格式
輸入檔案包含\(4\)行,每行\(4\)個字元,每個字元\(w\)或\(b\)表示遊戲開始時格子上物件的狀態。
輸出格式
輸出檔案僅一個整數,即從給定狀態到實現這一任務的最少翻轉次數。如果給定的狀態就已經實現了目標就輸出\(0\),如果不可能實現目標就輸出:\(Impossible\)。
樣例
\(input\)
bwwb
bbwb
bwwb
bwww
\(output\)
4
大致思路:
因為題目資料並不算大,可以暴力列舉水過去,但是我們提供一個效率高一點的思路:
首先我們先把第一行改成同白或同黑,這時第一行的狀態就確定了,那麼第二行的狀態也就確定了,按此向下推,到最後一行只需要判斷一下是否滿足要求,更新一下答案就可以了,其實程式碼思路比較清晰,一行一行向下推,暴力列舉也是可以的,要是資料範圍大一些可能會存在超時的可能,建議最好不要暴力列舉。
程式碼實現:
#include <bits/stdc++.h> const int maxn = 4 + 5, Inf = 1 << 16; char str[maxn][maxn]; int ans = Inf; char tmp[maxn][maxn]; void flip(char &ch) { if (ch == 'w') ch = 'b'; else if (ch == 'b') ch = 'w'; //必須是else if,不能兩個if } int cnt; void press(int x, int y) { cnt++; flip(tmp[x][y]); //主動翻轉第x行第y列,下面是被動翻轉 flip(tmp[x][y + 1]); flip(tmp[x][y - 1]); flip(tmp[x - 1][y]); flip(tmp[x + 1][y]); } void calc(int x, char ch) { //目標:全ch memcpy(tmp, str, sizeof(str)); //拷貝原陣列 cnt = 0; //計算翻轉次數 for (int i = 1; i <= 4; i++) //列舉每一列 if (x & (1 << (i - 1))) press(1, i); //第一行第i列需要翻轉 for (int i = 2; i <= 4; ++i) { //列舉2~4行 for (int j = 1; j <= 4; ++j) { //列舉每一列 if (tmp[i - 1][j] != ch) //如果上一行不是ch press(i, j); //需要通過下面這個翻轉 } } for (int i = 1; i <= 4; ++i) //如果第四行有不滿足的說明此種方案無效 if (tmp[4][i] != ch) return; ans = std::min(ans, cnt); } void Solve() { for (int i = 1; i <= 4; ++i) scanf("%s", str[i] + 1); for (int i = 0; i <= 15; ++i) { //一行的狀態是0~15 calc(i, 'w'); //第一行狀態為i,變成全w calc(i, 'b'); //第一行狀態為i,變成全b } if (ans == Inf) printf("Impossible\n"); else printf("%d\n", ans); } int main() { Solve(); return 0; }
B.強掠計劃
題目大意:
\(Siruseri\) 城中的道路都是單向的。不同的道路由路口連線。按照法律的規定,在每個路口都設立了一個 \(Siruseri\) 銀行的 \(ATM\) 取款機。令人奇怪的是, \(Siruseri\) 的酒吧也都設在路口,雖然並不是每個路口都設有酒吧。
\(Banditji\) 計劃實施 \(Siruseri\) 有史以來最驚天動地的 搶劫。他將從市中心出發,沿著單向道路行駛,搶劫所有他途徑的 \(ATM\) 機,最終他將在一個酒吧慶祝他的勝利。
使用高超的黑客技術,他獲知了每個 \(ATM\) 機中可以掠取的現金數額。他希望你幫助他計算從市中心出發最後到達某個酒吧時最多能搶劫的現金總數。他可以經過同一路口或道路任意多次。但只要他搶劫過某個 \(ATM\) 機後,該 \(ATM\) 機裡面就不會再有錢了。 例如,假設該城中有 \(6\) 個路口,道路的連線情況如下圖所示:
市中心在路口 \(1\) ,由一個入口符號 \(→\) 來標識,那些有酒吧的路口用雙圈來表示。每個 \(ATM\) 機中可取的錢數標在了路口的上方。在這個例子中, \(Banditji\) 能搶劫的現金總數為 \(47\),實施的搶劫路線是:\(1-2-4-1-2-3-5\)。
輸入格式
第一行包含兩個整數 \(N,M\)。\(N\) 表示路口的個數,\(M\) 表示道路條數。
接下來 \(M\) 行,每行兩個整數,這兩個整數都在 \(1\) 到 \(N\) 之間,第 \(i+1\) 行的兩個整數表示第 \(i\) 條道路的起點和終點的路口編號。
接下來 \(N\) 行,每行一個整數,按順序表示每個路口處的 \(ATM\) 機中的錢數 \(ai\)。
接下來一行包含兩個整數 \(S,P\),\(S\) 表示市中心的編號,也就是出發的路口。\(P\) 表示酒吧數目。
接下來的一行中有 \(P\) 個整數,表示 \(P\) 個有酒吧的路口的編號。
輸出格式
輸出一個整數,表示 Banditji 從市中心開始到某個酒吧結束所能搶劫的最多的現金總數。
樣例
\(input\)
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
\(output\)
47
題目思路:
這道題的主要思路就是tarjan先預處理一下。就這個例子說,我們可以看到1,2,4形成了一個權環,於是便可以把他們三個縮成一個點,然後再把原有的邊連起來,然後求一個從開始的點開始的單源spfa,最後列舉酒吧的錢數求最大值。思路很清晰,縮點+spfa,主要還是看模板打的熟不熟練了。
程式碼實現
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int Maxx = 1e7 + 10, INF = 0x3f3f3f3f;
int n, m;
int len, Head[Maxx], To[Maxx], from[Maxx], path[Maxx], t[Maxx];//cun
int len_, H[Maxx];
int low[Maxx], dfn[Maxx], stk[Maxx], top, num, belong[Maxx], cnt;//tarjan
long long w[Maxx];
bool vis[Maxx];
long long dis[Maxx];
struct Edge {
int to, next;
} edge[Maxx], E[Maxx];
void addedge1(int a, int b) {
edge[++len].to = b;
edge[len].next = Head[a];
Head[a] = len;
}
void addedge2(int a, int b) {
E[++len_].to = b;
E[len_].next = H[a];
H[a] = len_;
}
void tarjan(int u) {
low[u] = dfn[u] = ++num;
stk[++top] = u;
for (int x = Head[u]; x; x = edge[x].next) {
int v = edge[x].to;
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (!belong[v])
low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
++cnt;
while (1) {
int x = stk[top--];
belong[x] = cnt;
w[cnt] += path[x];
if (x == u) break;
}
}
}
void spfa(int s, int n) {
queue<int> que;
for (int i = 1; i <= n; i++) dis[i] = -INF, vis[i] = 0;
dis[s] = w[s];
que.push(s);
vis[s] = 1;
while (!que.empty()) {
int u = que.front();
que.pop();
vis[u] = 0;
for (int i = H[u]; i; i = E[i].next) {
int v = E[i].to;
if (dis[v] < dis[u] + w[v]) {
dis[v] = dis[u] + w[v];
if (!vis[v]) {
que.push(v);
vis[v] = 1;
}
}
}
}
}
void solve(){
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d%d", &from[i], &To[i]);
addedge1(from[i], To[i]);
}
for (int i = 1; i <= n; i++) {
scanf("%d", &path[i]);
}
int s, P;
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i);
scanf("%d%d", &s, &P);
s = belong[s];
int ansnum = 0;
for (int i = 1; i <= P; i++) {
int a;
scanf("%d", &a);
if (!vis[belong[a]]) {
t[++ansnum] = belong[a];
vis[belong[a]] = 1;
}
}
for (int i = 1; i <= m; i++) {
int u = belong[from[i]], v = belong[To[i]];
if (u != v) addedge2(u, v);
}
spfa(s, cnt);
long long ans = -INF;
for (int i = 1; i <= ansnum; i++) {
ans = max(ans, dis[t[i]]);
}
printf("%lld", ans);
}
int main(){
solve();
return 0;
}
C.測繪
題目大意
為了研究農場的氣候, Betsy 幫助農夫 John 做了N(1 <= N <= 100)次氣壓測量並按順序記錄了結果M_1--M_N(1 <= M_i <= 1,000,000), \(Betsy\) 想找出一部分測量結果來總結整天的氣壓分佈. 她想用 \(K(1 <= K <= N)\) 個數 \(s_j(1 <= s_1 < s_2 < ... < s_K <= N)\) 來概括所有測量結果. 她想限制如下的誤差:
對於任何測量結果子集,每一個非此子集中的結果都會產生誤差.總誤差是所有測量結果的誤差之和.更明確第說, 對於每一個和所有 \(s_j\) 都不同的 \(i\) :
* 如果 \(i\) 小於 \(s_1\), 誤差是: \(2 * | M_i - M_(s_1) |\)
* 如果 \(i\) 在 \(s_j\) 和 \(s_(j+1)\) 之間,誤差是: \(| 2 * M_i - Sum(s_j, s_(j+1)) |\)
注: \(Sum(x, y) = M_x + M_y\) ; ( \(M_x\) 和 \(M_y\) 之和)
* 如果 \(i\) 大於 \(s_K\) ,誤差為: \(2 * | M_i - M_(s_K) |\)
\(Besty\) 給了最大允許的誤差 \(E (1 <= E <= 1,000,000)\) ,找出最小的一部分結果使得誤
差最多為 \(E\).
輸入格式
第一行: 兩個空格分離的數: \(N\) 和 \(E\)
第 \(2..N+1\) 行: 第 \(i+1\) 行包含一次測量記錄: \(M_i\)
輸出格式
第一行: 兩個空格分開的數: 最少能達到誤差小於等於 \(E\) 的測量數目和使用那個測量數目能達到的最小誤差.
樣例
\(input\)
4 20
10
3
20
40
\(output\)
2 17
思路
首先我們要理解一點就是這道題是數量優先,能選最少的就選最少的,在數量最少的情況下再算誤差的最小,理解了這個再往下看,我一開始並沒有想到是個 \(dp\) 的題目,專心研究怎麼暴力(啊這),但是後來仔細一想,這真的就是一個赤裸裸的線性 \(dp\),首先我們定義dp陣列的含義, \(dp[i][j]\)表示的含義就是在前j個數裡選i個測試點的最小誤差,那麼再思考一個問題,\(dp[i][j]\) 這個狀態一定選了第 \(j\) 個節點作為測試點了(你品,你細品),否則這道題的轉移方程就很難寫...,轉移方程還要注意一個方面,不能只算前j個數的誤差,一定是把j之後的誤差都要算上,在這裡就不舉例子了,轉移方程:\(dp[i][j]=min(dp[i][j],dp[i-1][k]-p[k][N+1]+p[k][j]+p[j][N+1]\),其中p陣列是已經預處理好之後的狀態,講到這裡就沒什麼好說的了,首先先初始化,然後 \(dp\) 處理,具體看程式碼。
程式碼實現
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
long long N,E;
long long ans,dp[120][120],p[120][120];
long long a[120];
void Read_pri(){//初始化+輸入資料(沒寫快讀)
scanf("%lld%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++){
p[i][j] += abs(2 * a[k] - a[i] - a[j]);
}
}
for(int j = 1; j < i; j++){
p[i][0] += 2*abs(a[i]-a[j]);
}
for(int j = i + 1; j <= N; j++){
p[i][N+1] += 2*abs(a[j] - a[i]);
}
}
}
int main(){
Read_pri();
int id = N;
ans = INF;
for(int i = 1; i <= N; i++){//特判一下一個測試點是否可以滿足條件
dp[1][i] = p[i][0] + p[i][N+1];
if(dp[1][i] <= E){
id = 1;
ans = min(ans, dp[1][i]);
}
}
if(id == 1){
printf("%d %lld",id, ans);
return 0;
}
for(int i = 2; i <= N; i++){
for(int j = i; j <= N; j++){
dp[i][j] = INF;
for(int k = i - 1;k < j; k++){
dp[i][j] = min(dp[i][j],dp[i - 1][k] - p[k][N + 1] + p[k][j] + p[j][N+1]);
}
if(dp[i][j] <= E){
if(i > id) continue;
else if(i < id){
id = i;
ans = dp[i][j];
}else{
ans = min(ans, dp[i][j]);
}
}
}
}
printf("%d %lld",id, ans);
return 0;
}
D.獎學金
題目描述
小張學院有 \(c\) 名學生,第 \(i\) 名學生的成績為 \(ai\) ,要獲得的獎學金金額為 \(bi\) 。
要從這 \(c\) 名學生中挑出 \(n\) 名學生髮獎學金。這個神祕人物愛好奇特,他希望得到獎學金的同學的成績的中位數儘可能大,但同時,他們的獎學金總額不能超過 \(f\) 。
輸入格式
第一行有三個整數,分別表示要挑出的學生人數 \(n\) ,學生總人數 \(c\) 和獎學金總額的最大值 \(f\) 。
第 \(2\) 到第 \((c+1)\) 行,每行兩個整數,第 \((i+1)\) 行的整數依次表示第 \(i\) 名學生的成績 \(ai\) 和如果要給他發獎學金,則需要發的金額數 \(bi\) 。
輸出格式
輸出一行一個整數表示答案。如果無法滿足神祕人的條件,請輸出 \(−1\) 。
樣例
\(input#1\)
3 5 70
30 25
50 21
20 20
5 18
35 30
\(output#1\)
35
\(input#2\)
5 6 9
4 0
4 1
6 3
8 0
10 4
10 5
\(output#2\)
6
說明/提示
樣例 1 解釋
選擇成績為 \(5\) , \(35\) , \(50\) 的三名同學,獎金總額為 \(18+30+21=69\)。
資料規模
對於 30% 的資料,保證 n≤10^3,c≤2×10^3。
對於 100% 的資料,保證 3≤n≤10^5,n≤c≤2×10^5,0≤f≤2×10^9,0≤ai≤2×10^9,0≤bi≤10^5。
大致思路
首先要保證這道題要求中位數的最大值,那麼就是儘可能的放成績儘可能最高的那個,首先用\(sort\)根據成績來排序,一個堆把前\((n+1)/2\)的數放進大跟堆裡,然後從\((n+1)/2\)開始\(for\)迴圈來一次列舉成績高的,要是成績高而且獎學金需求還比大跟堆裡最大的金額少,那麼一定要放進去,並把價格最高的踢出來,保證剛放進去的數是第\((n+1)/2\)個數,直到搜到第\(c-(n+1)/2\)的時候,停止,維護了當前形式的最大中位數,再從後\((n+1)/2\)個數裡向前走,求出當前形式的最小中位數,然後跑一個\(for\)迴圈來求出最大的中位數即可。
程式碼實現
#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;
}