第二周训练总结
比赛
第四场个人赛
AC:
- A:水题,签到题
- B:枚举,枚举每两个字符串,如果这两个串没有同一位都是 $x$,答案就加一
- C:模拟,用一个 $flag$ 记录遍历到的引号是否为奇数下标,然后用.去替换,即可
- I:分类讨论,分别判断字符串长度、首位字符、第二个字符以及其余字符即可
- J:模拟,首先预处理一下输入的 $m$ ,减去
m/tot * tot
,最后遍历一遍数组即可
补题:
D:
-
题意:给你一个有$N$个顶点和$M$条边的简单无向图$G$,你可以连接任意原本没有边的点$u$、$v$,使得结果是一个二分图,打印有几种连接的方法。
-
重点是将其转化为二分图模型,我们考虑三种情况:
- 当前只有一个联通分量,且该联通分量内本身为一个二分图:
此时我们只需要染色后得到c1,c2(将图内的点分为两部分),(c1*c2-m)
就是答案,表示所有满足二分图的边除去已有的边。 - 当前有多个联通分量,且各联通分量内部都为二分图:
此时我们对其中一个联通分量染色后得到(res1 += (c1*c2))
所有可能的边,(res2 += (c1+c2) * (n-(c1+c2)) )
当前联通分量可以和其他联通分量之间内连多少条边
最终我们能够得到res1(所有可能连的边),res2(各联通分量之间连起来的边),但是因为res2在求解的过程中,各联通分量会对同一条边计算两次,所以我们最终的答案为:ans = res1+res2/2-m
; - 如果任意联通分量中存在非二分图,那么不论怎么加边都无法保证整个图内没有长度为奇数的回路,所以答案是0
- 当前只有一个联通分量,且该联通分量内本身为一个二分图:
-
具体代码使用dfs实现即可
-
code
// 染色 + 计数 bool dfs(int u,int col){ g[u] = col; c1 += col==1; c2 += col==2; for(auto v:e[u]){ if(g[v] == g[u]) return false; if(!g[v]){ if(!dfs(v,3-col)) return false;; } } return true; } // main int main() { for(int i = 1;i <= n;++i){ if(!g[i]){ c1 = c2 = 0; if(!dfs(i,1)){//如果存在非二分图,无法加边 cout<<0<<endl; return; }; res1 += c1*c2; res2 +=(c1+c2)*(n-c1-c2); } } int ans = res1+res2/2-m;//第一二种情况合并起来,因为如果只有一个联通分量,res2为0 }
第五场个人赛
AC:
- A:模拟,给出两种操作:A跟随B或者A取消跟随B,然后询问A和B两人是否相互跟随,使用
map<pair<int, int>, int>
存储是否跟随即可 - B:思维,模拟,每次可以整体更改所有的数或者更改一个数,那么可以使用一个set维护那些被整体更改过的数来防止TLE
- C:模拟,给一个时间,询问最近的时间,该时间将小时的低位与分钟的高位更换之后还能是正常的时间,直接模拟枚举判断
- D:图论,使用bfs遍历即可
- F:签到题,给一些字符串和规则,判断他们是否合法
补题:
E题总是没做对,其他的还没补...
E:
-
题意:手里有很多卡牌,第一张可以随意放到桌子上,之后每次放的卡牌必须与上一次放的卡牌点数相同或者+1 mod m,询问最后手里剩的牌的最小点数是多少
-
做法:使用前缀和差分,遍历每一个手里的数,进行多个分支判断,具体代码:
int main() { for (int i = 1; i <= n; i++) { if (cha[i] >= 2) { tmp = sum[i - 1] - sum[curst - 1]; if (tmp >= res) { st = curst; ed = i - 1; res = tmp; } curst = i; } if (i == n && cha[i] >= 2) { tmp = a[n]; if (tmp >= res) { st = n; ed = n; res = tmp; curst = n; } if (tmp == m - 1 && a[1] < 1) { long long te = 0; for (int i = 1; i < curst; i++) { if (cha[i] <= 1) { te += a[i]; } else break; } tmp += te; if (tmp > res) { cout << sum[n] - tmp << endl; return 0; } } } if (i == n && cha[i] <= 1) { tmp = sum[i] - sum[curst - 1]; if (tmp >= res) { st = curst; ed = i; res = tmp; } } } if (ed == n && a[n] == m - 1 && a[1] < 1 && st > 1) { long long te = 0; for (int i = 1; i < st; i++) { if (cha[i] <= 1) { te += a[i]; } else break; } res += te; } cout << sum[n] - res << endl; }
第六场个人赛
AC:
-
A:模拟,思维,给一棵树,第 $i$ 个节点的儿子是 $2i$ 和 $2i+1$ ,求每个节点的深度,关键代码:
dep[(i + 1) * 2] = dep[a[i]] + 1; dep[(i + 1) * 2 + 1] = dep[a[i]] + 1;
-
B:dp,将横坐标纵坐标分开考虑,$dpx(i, j)$ 表示在x方向上,通过前 $i$ 个线段能否到达 $j$ 坐标,注意坐标要加10000的偏移量,保证非负,状态转移:
dpx[i][j] = dpx[i - 2][j - p[i]] | dpx[i - 2][j + p[i]]
,纵坐标同理 -
D:枚举,数据量较小,暴力
-
E:简单题,给一个数组,问任意两个和最大且为偶数,输出和,正确解法为将奇数和偶数分开来存,然后奇数最大的两个加,偶数最大的两个加,再取max。wa了一发的原因是没有分开讨论,直接用一整个数组进行从大到小的枚举。
补题:
G:
-
最初是在(1,1)的位置上面,每一次移动只能移动到与该点距离的平方为m的位置,问是否能到达所有的位置,要是可以,输出到达所有的位置的最小步数,要是不行,输出-1。
-
使用bfs,先初始化所有位置的步数为最大值,存储到达所有位置的最小路径,如果说走完之后还有点的步数等于最大值,则输出-1,否则输出这个棋盘。
-
核心代码:
void bfs() { q.push({0, 0}); while(!q.empty()) { int x = q.front().first, y = q.front().second; q.pop(); for (int i = 0; i < dx.size(); i++) { int cntx = x+dx[i]; int cnty = y+dy[i]; if(check(cntx, cnty)) continue; if(dp[cntx][cnty] != -1) { continue; } dp[cntx][cnty] = dp[x][y] + 1; q.push({cntx, cnty}); } } }
第二场组队赛
因个人原因,只做了2题
AC:
- G:每次取最大的数和最小的数,将最大的变成“大mod小”,如果是0,直接扔掉,问几次才能把整列数组的个数变为1;使用deque维护,复杂度 $O(nlogn)$
补题:
-
A:比较简单,差分+同余:
int main() { for(int i = 1; i < n; ++i) { cha[i] = a[i] - a[i - 1]; // cout << cha[i] << " "; } // cout << endl; cha[0] = a[0]; int g = gcd(cha[1], cha[2]); // cout << g << endl; for(int i = 3; i < n; ++i) { g = gcd(g, cha[i]); // cout << g << " "; } if(g == 1) cout << 2; else cout << 1; }
-
B:一开始以为是dp,但是数据量较小(5000),于是可以暴力枚举就行:
int main() { for(int i = l; i < s.size(); i++){ if(s[i] == 'p'){ for(int j = l; j <= i; j++){ if(s[j] == 'p') st[i - (j - l)] = 'd'; else st[i - (j - l)] = 'p'; } if(st < ans) ans = st; } } }
训练
学习了一些基础的dp,总结了一些代码和文字:
- 状压dp:状态压缩动态规划,就是我们俗称的状压DP,是利用计算机二进制的性质来描述状态的一种DP方式。
- 例题:在 n×n(1<=n<=10) 的棋盘上放 k(0<=k<n×n)个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=160;
int f[11][maxn][maxn];
int num[maxn],s[maxn];
int n,k,cnt;
void init(){ //预处理一下没有其他行限制下每一行的可能情况有多少种
cnt=0;
for(int i=0;i<(1<<n);i++){
if(i&(i<<1)){ // 代表左右有相邻国王
continue;
}
int sum=0;
for(int j=0;j<n;j++){ //枚举一下i这个情况下哪些地方是国王
if(i&(1<<j)){
sum++;
}
}
s[++cnt]=i; //s[cnt]代表了第cnt种情况下的状态
num[cnt]=sum;
}
// cout<<"cnt "<<cnt<<"\n";
}
void solve(){
cin>>n>>k;
init();
f[0][1][0]=1; //代表第0行在num[1]即放了0个国王的情况有1种
for(int i=1;i<=n;i++){ //枚举行
for(int j=1;j<=cnt;j++){ //枚举这一行有多少种情况
for(int l=0;l<=k;l++){ //枚举算上这一行的国王总数
if(l>=num[j]){ //算上这一行放的国王总数起码得大于等于这一行自己就有的国王个数
for(int t=1;t<=cnt;t++){ //枚举上一行的情况
//1.不能跟上一行有列重合 2.不能刚好差一行
if(!(s[t]&s[j])&&!(s[t]&(s[j]<<1))&&!(s[t]&(s[j]>>1))){
f[i][j][l]+=f[i-1][t][l-num[j]];
}
}
}
}
}
}
int ans=0;
for(int i=1;i<=cnt;i++){
ans+=f[n][i][k];
}
cout<<ans<<"\n";
}
signed main(){
int t;
t=1;
while(t--){
solve();
}
}
- 数位dp:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 15
int num;
int* a = new int[maxn];
int f[15];
//int a[maxn];
int b[maxn];//b保存第p为存的是那个数
int ten[maxn];
int L, R;
int t;
int dfs(int p, bool limit) {//p表示在第p位,limite表示此时是否处于限制位
if (p < 0) {
//for (int i = 2; i >= 0; i--)cout << b[i];//无限递归,记得加结束return
//cout << endl;
return 0;//搜索结束,返回
}
if (!limit && f[p] != -1) {//记忆化搜索,不处于限制位,并且f[p]被算过了
return f[p];
}
int up = limit ? a[p] : 9;//判断是否处于限制位,如果是就只能取到a[p]为,否则最高位能取到9
int ans = 0;
for (int i = 0; i <= up; i++) {
//b[p] = i;
if (i == 3) {
if (limit && i == up) {
ans += 1;
for (int j = p - 1; j >= 0; j--)//处于限制条件就把限制数下面全算上
ans += a[j] * ten[j];
}
else//如果不处于限制条件直接加上10的p次方
ans += ten[p];
}
else ans += dfs(p - 1, limit && i == up);//这里填a[p]可以填up也行,在处于限制的时候up等于a[p]
}
if (!limit)//记忆化搜索,如果没有处于限制条件就可以直接那算过一次的数直接用,能节省很多时间
f[p] = ans;
return ans;
}
int handle(int num) {
int p = 0;
while (num) {//把num中的每一位放入数组
a[p++] = num % 10;
num /= 10;
}
//说明a数组写进去了,但是读取无效数据是什么意思勒,之前好像不是这样的,解决办法,动态创建数组
/*for (int i = 0; i < p; i++) {
cout << a[i];
}*/
return dfs(p - 1, true);//对应的最高位为p-1位,为True表示没有处于限制位
}
void init() {
ten[0] = 1;
for (int i = 1; i < 15; i++) {
ten[i] = ten[i - 1] * 10;
}
memset(f, -1, sizeof(f));
}
int32_t main() {
cin>>t;
while(t--){
cin>>L>>R;
//handle(23);
init();//一定要记得初始化,TM的我在这卡了半个月
cout << handle(R)-handle(L) << endl;
delete[]a;
}
return 0;
}
标签:总结,tmp,num,训练,int,long,第二周,maxn,dp
From: https://www.cnblogs.com/marti88414/p/17569377.html