牛客算法課 (算法入門班) 貪心與模擬(3)

本文讨论了使用二进制表示矩阵进行按鈕操作优化,类似扫雷问题的解法,并应用位运算法实现FlipGame。还涉及了CF333E中关于画圆不相交问题的解法,利用bitset数据结构处理三角形边长。最后是月月查华华手机问题,通过记录字符出现位置解决子序列查找问题。
摘要由CSDN通过智能技术生成

目录

Flip Game

CF333E

月月查华华的手机


Flip Game

我們考慮使用二進制的表示整個矩陣, 然後透過位運算來實現按按鈕的變化.

由於同一個按鈕, 按兩次就會等於沒有按過, 所以我們所有按鈕只會按一次. 還有每一個按鈕可以影響的區域為上下左右, 所以與隔離自己超過兩格的地方都沒有直接關係. 因此我們考慮, 我們每一次都會在第i行, 嘗試把第i - 1 行所有格子變為黑, 或全白.

那麼剩下的難題就是第一行應該怎麼去改? 考慮所有可能性, 我們只有第一行要枚舉可能性, 剩下的行數就已經可以確認他們的狀態了, 這就跟掃地雷的那一題是一樣的原理.

由於題目是問操作次數, 所以我們把每一行操作中的 二進制中的 1 數出來, 就是操作次數了.

要注意的地方是實現代碼的時候, 我們一開始是枚舉第一行的情況, 然後, 接下來的行數都是圍繞這一個第一行而變化的, 所以我們要另開數組, 而且不打亂本來的第一行的情況. 不然都會不準確.

比如說 第一行原本是1011, 然後你把他改成1101了, 然後第二次你去枚舉第一行其他改變, 你應該是按照本來的1011去改的, 而不是說1101去改.

如果對位運算思路清晰, 實現難度並不大.

如何利用位運算達到flip的效果呢?

有1的地方表示我們要取反

設change = 1010 這個操作, 就是把第一位flip 和第3位flip, 假設原來的黑白情況為 a = 0110.

那麼我們是不是要第一位的左和右變成和原本相反?

先把要變色的位置取反, change^a, a 變成 1100. 第一位和第三位都取反了.然後就到左右了.

change>>1 = 0101, 表示把要改變的位置的右邊取反. 如何把a的東西取反呢? (change>>1)^a

之後a就會變成 1001.

然後我們把左邊也取反(change<<1)^a , change<<1 = 10100, 很明顯, 我們這裡多了一個前導1, 要把消除掉就要用 and 這個操作, and 01111, 這個操作 是只有當兩個二進制位的位置都是1 才會是1, 不然就是0, 你會發現我們就可以在不影響後4位的情況下, 把前導1 刪除.

所以 (change<<1)^a^((1<<m)-1)就是把左邊取反. 所以a變成  1101

所以我們手動做一次看看對不對 原本wbbw, 我們換第1位, bwbw, 然後換第3位, bbwb, 就跟我們的推論是一樣的.

這裡怕你們不了解xor操作, 給一個真值表

你會發現無論第一個數字他是0, 還是1, 只要異或1, 永遠出一個相反的數字. 

代碼:

#include<iostream>
#include<vector>
using namespace std;
const int n = 4, m = 4;
int change[n + 10];
int countOne(int x){
    int cnt = 0;
    while(x){
        if(x&1==1)cnt++;
        x>>=1;
    }
    return cnt;
}
int work(int a[]){
    int cur[n + 10];
    int cnt =0;
    int ans = 1e9;
    for(change[0] = 0; change[0] < (1 << m) -1; change[0]++){
        cnt = countOne(change[0]);
        cur[0] = a[0]^change[0]^(change[0]>>1)^((change[0]<<1)&((1<<m)-1));
        cur[1] = a[1]^change[0];
        for(int i = 1; i < n; i++){
            change[i] = cur[i - 1];
            cnt+=countOne(change[i]);
            cur[i] = cur[i]^change[i]^(change[i]>>1)^((change[i]<<1)&((1<<m) - 1));
            cur[i + 1] = a[i + 1] ^ change[i];
        }
        if(cur[n - 1]==0){
            ans = min(ans, cnt);
        }
    }
    return ans;
}
int main(){
    int a[n + 4], b[n + 10];
    memset(a, 0, sizeof(a));
    memset(b, 0, sizeof(b));
    for(int i= 0; i < 4; i++){
        for(int j =0; j < 4; j++){
            char c;
            cin >> c;
            if(c == 'b')a[i]|=(1<<j);
            else b[i]|=(1<<j);
        }
    }
    int ans = min(work(a), work(b));
    if(ans > n*m)cout << "Impossible" << endl;
    else cout << ans << endl;
}

CF333E

在一个平面内给出n个点的坐标,任选其中三个为圆心作半径相同的圆,要求这三个圆不能相交但可以相切,求能画出的圆中的最大半径。

有於不可以相交所以我們要取這三個圓心構成的三角形中最短的一條邊. 如果取最長會有相交的風險.

然後我們發現把任意兩個點連成邊, 然後按照他們的長度排序, 如果找到符合條件的邊他必然是最長的. 所以我們先排序, 然後如果發現他們有公共點, 證明可以組成三角形, 直接返回答案.

那麼怎麼可以用最短的時間記住他們有沒有相連呢? 

利用bitset, 他是一個很長的01串, 並且支持位操作.

#include<iostream>
#include<vector>
#include<bitset>
#include<cmath>
#include<algorithm>
using namespace std;
int n, m = 0;
int x[4000], y[4000];
bitset<4000> b[4000];
struct ty{
	int len;
	int x, y;
}a[3000*3000];
double dis(int i, int j){
	return (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]);
}
bool cmp(ty a, ty b){
	return a.len > b.len;
}
int main(){
    cin >> n;
    for(int i = 1; i <= n; i++){
    	cin >> x[i] >> y[i];
	}
	for(int i = 1; i <= n; i++){
		for(int j = i + 1; j <= n; j++){
			a[++m].len = dis(i, j);
			a[m].x = i;
			a[m].y = j;
		}
	}
	sort(a + 1, a + 1 + m, cmp);
	for(int i = 1; i <= m; i++){
		if((b[a[i].x] & b[a[i].y])!=0){
			printf("%.8f", sqrt(a[i].len)/2.0);
			break;
		}else{
			b[a[i].x][a[i].y] = 1;
			b[a[i].y][a[i].x] = 1;
		}
	}
}

月月查华华的手机

我們的思路是記錄, 每一個英文字母第一次出現的位置, 然後再把這個英文字母後面所有的英文字母第一次出現的位置記下來.

如果我們要看一個子序列是否在裡面, 我們可以不斷查看這個子序列裡的字母是否可以一直查詢下去. 

比如我們要查詢的子序列是aabc 而我們的字符串是aaaacd, 我們會知道a之後只出現了 c和d, 並沒有b, 當我們查詢的時候就會發現找不到b, 然後就表示不可能是YES.

#include<bits/stdc++.h>
using namespace std;
int main(){
    string s;
    cin >> s;
    int n;
    cin >> n;
    vector<vector<int>> nxt(1000050, vector<int>(30, -1));
    int last[30];
    memset(last,-1,sizeof last);
    for(int i = s.length() - 1; i >= 0; i--){
        for(int j = 0; j < 26; j++){
            nxt[i][j] = last[j];
        }
        last[s[i] - 'a'] = i;
    }
    while(n--){
        string s1;
        cin >> s1;
        int pos = last[s1[0] - 'a'];
        if(pos == -1){
            cout << "No" << endl;
            continue;
        }
        for(int i = 1; i < s1.length(); i++){
            pos = nxt[pos][s1[i] - 'a'];
            if(pos == -1){
                cout << "No" << endl;
                break;
            }
        }
        if(pos!= -1)cout << "Yes" << endl;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值