矩阵消除游戏(位运算+贪心)
一开始的想法是在每个回合中,统计行和列的最大值,比较取出,然后所在行或列为0,再进行下一轮回合,直至结束。
错误原因:该方法不满足无后效性,就好像装箱问题:给你一个体积为V的箱子,再给你若干件体积不同的物品,希望选一些物品尽量装满,你会上来就选择最大的吗?我们会很容易的想到,也许拼两个小的比装一个大的更好。对于做个题其实也一样,你选了最大的,后面选的就少了……
例子:
3 3 2
100 100 1
2 4 2
4 2 2(难受)
我们知道如果只是行的选择或者只是列的选择就可以贪心(满足无后效性)。那么我们就把问题转化成只选若干列好了!
如果我们想贪心的选取最好的若干列,那么我们必须提前知道我们选了多少行哪些行,这个怎么知道呢?看看数据范围,其实暴力就好了——写个搜索,或者用01串枚举。
搜索我就不说了哈(懒癌晚期),我来细细讲讲01串枚举:
所谓01串枚举,就是我们在每个个体都面对两种选择的时候,可以用一个01串表示,比如说对于本题,每一行有选和不选两种可能,假设有5行的话,我们就可以用一个长度为5的01串来表示,0表示不选1表示选,就像一个bool数组一样,如:01001 表明第134行不选,第25行要选。
只是用一个数字来表示比用数字表示方便,为什么方便呢?因为所有长度小于n的01串,转成十进制之后就是所有小于2^n 的数字,这样从0到2^n - 1枚举数字就是从00…00枚举到11…11。这样比写个搜索枚举所有选和不选的情况快乐吧!
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[20][20], row[20], col[20];
bool st[20];
int deal(int x)
{
memset(st, 0, sizeof st);
int cnt = 0, idx = 1;
while(x)
{
if(x & 1) cnt ++, st[idx] = 1;
x >>= 1;
idx ++;
}
return cnt;
}
int main()
{
int n, m, k;
cin >> n >> m >> k;
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= m; j ++ )
cin >> a[i][j], row[i] += a[i][j];//先统计行(行列顺序无所谓)
int temp = (1 << n) - 1;//(2^n - 1)
long long ans = 0;
if(k >= n || k >= m)
for(int i = 1; i <= n; i ++ )
ans += row[i];
else
{
for(int i = 0; i <= temp; i ++ )
{
long long sum = 0;
int cntr = deal(i);//范围数字i的二进制形式中有多少1
int cntc = k - cntr;//需要多少列
if(cntc < 0 || cntc > m) continue;
for(int i = 1; i <= n; i ++ )
if(st[i]) sum += row[i];
memset(col, 0, sizeof col);
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= m; j ++ )
if(!st[i]) col[j] += a[i][j];//注意col[j]是j!!统计的是列
sort(col + 1, col + m + 1);
for(int i = 1, j = m; i <= cntc; i ++, j -- ) sum += col[j];
ans = max(ans, sum);
}
}
cout << ans << endl;
return 0;
}
删除子序列
谁能想到这题能用dp写啊,谁能想到啊
dp[j]表示:T字符串长度为j时能删除的次数
属性为min
状态计算为:dp[j] = min(dp[j - 1], dp[j] + 1);
解释:当S[i]与T[j]相等时,dp[j]要么从dp[j - 1]转移过来,要么从当前i位置匹配成功的dp[j]开始,但要保证是min
例如T = abc dp[0] = 3, dp[1] = 1, 那么方案书就是dp[1] + 1;
dp[0] = 0, dp[1] = 0, 那么方案书必须是从dp[0]过来
(怎么感觉像贪心啊)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[30];
int main()
{
int ts;
cin >> ts;
while(ts --)
{
memset(dp, 0, sizeof dp);
int n, m;
cin >> n >> m;
string a, b, c;
cin >> c >> b;
for(int i = 0; i < c.size(); i ++ )
for(int j = 0; j < b.size(); j ++ )
{
if(c[i] == b[j])
{
if(j == 0) dp[j] += 1;
else dp[j] = min(dp[j - 1], dp[j] + 1);
}
}
cout << dp[m - 1] << endl;
}
return 0;
}
小沙的构造
这题做的真难受啊!!!!
首先判断n是否是奇数,如果是需要特殊处理。
其次,尽可能的把<{[这样的加进去,但当n = 3, m = 2这种情况就不能放进去了。除此之外,n = 2, m = 2的这种情况也需要特判一下,要不然也会出错。
不得不说,真细啊这题。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
char ch[10010];
int main()
{
int n, m;
cin >> n >> m;
string str = "<\\[{(!\"'*+-.08:=^_WTYUIOAHXVM|", str1 = ">/]})!\"'*+-.08:=^_WTYUIOAHXVM|";
string a = "<\[{(", b = ">/]})";
if(m >= 36) {puts("-1"); return 0;}
int idx = 0, len = 0;
if(n & 1) ch[n / 2] = str[5], m --;
int ans = n - 1;
while(m > 2 && idx < 5 && len < n / 2 || (m == 2 && (n / 2 - len) == 1))//当len == 3 m == 2就不能在继续了
{//||后面的条件也不能缺
ch[len] = str[idx], m -= 2;
ch[ans - len] = str1[idx];
len ++, idx ++;
}
idx = n & 1 ? 6 : 5;
for(int i = len; i < n / 2; i ++ )
{
if(m)
ch[i] = str[idx ++ ], m --;
else ch[i] = str[idx - 1];
ch[ans - i] = ch[i];
}
if(m){ puts("-1"); return 0;}
cout << ch;
return 0;
}
红色警戒
坑:失去最后一个城市才会输出“Game Over.”
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m;
bool g[510][510];
int p[510];
int find(int x)
{
if(p[x] != x) return p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++ ) p[i] = i;
while(m -- )
{
int a, b;
cin >> a >> b;
g[a][b] = g[b][a] = true;
int pa = find(a), pb = find(b);
p[pa] = pb;
}
int cnt = 0;
for(int i = 0; i < n; i ++ )
if(p[i] == i) cnt ++;
int t;
cin >> t;
int res = t;
while(t -- )
{
int nu;
cin >> nu;
for(int i = 0; i < n; i ++ ) p[i] = i;
for(int i = 0; i < n; i ++ ) g[nu][i] = g[i][nu] = false;
for(int i = 0; i < n; i ++ )
for(int j = 0; j < n; j ++ )
if(g[i][j])
{
int pa = find(i), pb = find(j);
p[find(i)] = find(j);
}
int ans = 0;
for(int i = 0; i < n; i ++ ) if(p[i] == i) ans ++;
// for(int i = 0; i < n; i ++ ) cout << p[i] << ' ';
// cout << ans;
if(ans == cnt + 1 || ans == cnt) printf("City %d is lost.\n", nu);
else printf("Red Alert: City %d is lost!\n", nu);
cnt = ans;
if(t == 0 && res == n) printf("Game Over.\n");
}
return 0;
}
病毒溯源
邻接表存储,dfs搜索
但一开始不知道如何在搜索过程中去判断两个相同长度的序列的大小如何去比较,并且如何保存搜索过程中的序列,我把搜索的回溯给忘了,完蛋玩意。
回溯回溯回溯回溯回溯,不能再忘了,求求了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
const int N = 1e4 + 10;
int h[N], ne[N], e[N], idx;
set<int> se;
vector<int> v, temp;
int ans;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int nu, int len)
{
if(len > ans){ans = len; v = temp;}
//当搜索过程中的长度大于ans,v就用temp替换
if(len == v.size() && v > temp)
v = temp;
//vector容器自带比较功能,从第一个元素开始两两比较,解决了比较的问题
for(int i = h[nu]; i != -1; i = ne[i])
{
int j = e[i];
temp.push_back(j);
dfs(j, len + 1);
temp.pop_back();
//关键问题还是这个,回溯来控制搜索过程中的某个时刻从源点到j的序列,很重要啊啊啊啊啊啊
}
return ;
}
int main()
{
int n;
cin >> n;
memset(h, -1, sizeof h);
for(int i = 0; i < n; i ++ )
{
int k, nu;
cin >> k;
for(int j = 0; j < k; j ++ )
{
cin >> nu;
se.insert(nu);
add(i, nu);
}
}
for(int i = 0; i < n; i ++ )
{//判断那个点为起始点
if(se.count(i) == 0)
{
temp.push_back(i);
dfs(i, 1);
break;
}
}
cout << ans << endl;
for(auto it = v.begin(); it != v.end(); it ++)
{
if(it != v.begin()) cout << ' ';
cout << *it;
}
return 0;
}
凯撒密码
#include <bits/stdc++.h>
using namespace std;
int main()
{
string str;
getline(cin, str);
int n;
cin >> n;
n %= 26;
//对n%26,讲n的范围控制到0到25(n = 26时,字母还是其本身)
if(n < 0)
n += 26;
for(int i = 0; i < str.size(); i ++ )
{
if(str[i] >= 'a' && str[i] <= 'z')
{
str[i] = (str[i] + n) % ('z' + 1);
//将str[i]的范围控制在<= z中
if(str[i] < 'a') str[i] += 'a';
//如果str[i]的结果小于a,那么加上a
}
else if(str[i] >= 'A' && str[i] <= 'Z')
{//同理
str[i] = (str[i] + n) % ('Z' + 1);
if(str[i] < 'A') str[i] += 'A';
}
}
cout << str << endl;//搞不懂为什么要加一个endl,要不然就错了
return 0;
}
有一个更好理解的代码
但我不理解为什么先用ch判断范围会提示错误
//char ch = str[i] + n;
if(str[i] >= 'a' && str[i] <= 'z')
{
if(str[i] + n >= 'a' && str[i] + n <= 'z') str[i] = str[i] + n;
else if(str[i] + n > 'z') str[i] = str[i] + n - 26;
else if(str[i] + n < 'a') str[i] = str[i] + n + 26;
}
else if(str[i] >= 'A' && str[i] <= 'Z')
{
if(str[i] + n >= 'A' && str[i] + n <= 'Z') str[i] = str[i] + n;
else if(str[i] + n > 'Z') str[i] = str[i] + n - 26;
else if(str[i] + n < 'A') str[i] = str[i] + n + 26;
}
Longest Strike
题意是从l~r中每个数都出现至少k次以上,问序列的l和r是多少
先把序列a中每个数出现的次数统计出来,大于等于k的单独拎出来放到vector中,对vector从小到大排序,因为题目要求从l~r连续的区间,所以如果相邻两元素差值不为1,则需要判断+记录l和r
for(auto it: ma){
//这样写是真的好用,都给我学起来 auto访问的时候用"."
}
v.back();//还有vector的最后一个元素表示方法
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
using namespace std;
int main()
{
int t;
cin >> t;
while(t -- )
{
int n, m;
cin >> n >> m;
map<int, int> ma;
for(int i = 0; i < n; i ++ ){
int nu; cin >> nu;
ma[nu] ++;
}
vector<int> v;
for(auto it: ma){//这循环写的真是省时间
if(it.second >= m)
v.push_back(it.first);
}
sort(v.begin(), v.end());
int len = 1, ans = -1;
int l, r;
if(v.size() == 0) {cout << -1 << endl;continue;}
for(int i = 0; i < v.size() - 1; i ++ )
{
if(v[i] == v[i + 1] - 1){
len ++; continue;
}
else {
if(len > ans) ans = len, l = v[i] - len + 1, r = v[i];
//利用连续的性质,v[i] - len + 1就是l
len = 1;
}
}
if(len > ans) {r = v.back(); l = r - len + 1;}
cout << l << ' ' << r << endl;
}
return 0;
}