Loading

第一周训练总结

第一周训练总结

比赛

个人赛1

AC题目:

  • A:签到题,给你每天的步数,算出每七天一共走多少步,一开始用一维数组wa了两发,不知道为啥,改用二维数组过了
  • B:暴力枚举,枚举每两个字符串拼接(两种情况)
  • D:模拟:用栈判断每个闭合的左括号和右括号,最后输出栈中内容

补题:

E:
  • 题意:数学题,n个人围成圈,m个数,每相邻两个人不能拥有同一个数,问一共有几种方法

  • 分析:首先将环化成链,那么每个人的情况很好求 \(m*(m-1)^n\) ,然后把链首尾相接,这样有两种情况:

    • 如果相同那么算作合为一个格子,就是环的 \(m-1\) 种情况的答案
    • 如果不同则为环的 \(m\) 种情况答案
    • 总的方案数是这两种情况相加

    推出这个就可以进行递推以及进行排列组合消元了(人在外地,写的公式没带着...)

  • 核心代码:

ans += pow(-1, n) * (m-1);
ans = (ans + mod) % mod;

注意一定要有第二行,不然有可能ans会减成负数,就是因为这个wa了两发到最后都没过去

C:
  • 题意:有两个床单a,b,每个床单存在两种格子:透明的或者黑色的,把他们进行平移拼接(可以有重叠部分),问最后的黑格子能否与第三个床单x完全重合
  • 枚举,枚举ab所有的可能的拼接方式,然后判断是否能和x重合即可,床单的大小比较小所以不会wa
  • 代码:
#include <iostream>
#include <string.h>
#define N 37
using namespace std;

string s;
int ans[N][N];
int ha, wa, hb, wb, hc, wc;
int a[N][N], b[N][N], c[N][N];

int main() {
	cin >> ha >> wa;
	for(int i=1; i<=ha; i++) {
		cin >> s;
		for(int j=0; j<wa; j++)
			a[i][j + 1] = (s[j] == '#');
	}
	cin >> hb >> wb;
	for(int i=1; i<=hb; i++) {
		cin >> s;
		for(int j=0; j<wb; j++)
			b[i][j + 1] = (s[j] == '#');
	}
	cin >> hc >> wc;
	for(int i=1; i<=hc; i++) {
		cin >> s;
		for(int j=0; j<wc; j++)
			c[10 + i][11 + j] = (s[j] == '#');
	}
	for(int i=1; i<=hc+10; i++)
		for(int j=1; j<=wc+10; j++)
			for(int k=1; k<=hc+10; k++)
				for(int l=1; l<=wc+10; l++) {
					memset(ans, 0, sizeof ans);
					for(int p=1; p<=ha; p++)
						for(int q=1; q<=wa; q++)
							if (a[p][q])
								ans[i + p - 1][j + q - 1] = 1;
					for(int p=1; p<=hb; p++)
						for(int q=1; q<=wb; q++)
							if (b[p][q])
								ans[k + p - 1][l + q - 1] = 1;
					int f = 1;
					for(int p=1; p<=30; p++)
						for(int q=1; q<=30; q++)
							if (ans[p][q] != c[p][q])
								f = 0;
					if (f){
						cout << "Yes" << endl;
						return 0;
					}
			}
	cout << "No" << endl;
	return 0;
}

个人赛2

AC题目:

  • A:签到题,输出间隔小于d的数

  • B:签到题,8个字符的字符串,满足所说的两个条件即可

  • C:贪心,把PC放在左右连续的两个T上就可以

  • D:思维题,ab两个数,每次把大的变成大-小,核心代码:

    	while(a != b) {
    		if ( a == 1 || b == 1) {
    			cnt += a == 1 ? b - 1 : a - 1;
    			break;
    		}
    		else if(a > b) {
    			if ( a % b == 0) {
    				cnt += a / b - 1;
    				break;
    			}
    			cnt += a/b;
    			a -= a/b * b;
    		//	cout << "a " << a << endl;
    		}
    		else {
    			if( b % a == 0) {
    				cnt += b / a - 1;
    				break;
    			}
    			cnt += b/a;
    			b -= b/a * a;
    		//	cout << b << endl;
    		}
    		
    	}
    

    注意a,b为0的情况

  • E:贪心,枚举,每次枚举最小的情况,然后把最小+原始数组放到一起,循环到k,使用set维护即可

补题:

F:
  • 题意:k个黑点随机分布,价值定义为最小能框住所有黑点的最小矩形的长乘宽,输出价值的期望
  • \(f(i, j)\) 定义为能框住 \(i * j\) 大小方格的矩形,里面(使用容斥原理计算,例如:第一列必须有格子,第一行必须有格子...等等,四个容斥原理),\(g(i, j)\) 表示这样的矩形有多少个
  • 使用上面两个函数相乘,再乘上一个排列组合的系数 即可求得答案

个人赛3

AC题目

  • A:签到题,0换成1,1换成0
  • B:并查集,每个连通分量划入一个并查集,从大到小输出
  • C:模拟枚举,使用mulitset维护就可以了
  • D:dp/dfs,简单dp,最开始dfs wa了5发,问了同学发现dfs可以狠狠剪枝通过

补题

I
  • 题意:给一个01串,每次可以把不相邻的两个反转,问最少多少次能反转成全0

  • 分析:首先判断能不能,每次反转两个,0变1,1变0,因此1数量的奇偶性不变,判断一开始1的奇偶性即可;其次判断有几个1,如果有2个以上则直接输出1的个数/2;否则分类讨论几种情况,下面贴出核心代码:

    		else if(cnt==2){
    			int x=-1,y;
    			for(int i=0;i<len;++i){
    				if(s[i]=='1'&&x==-1)x=i;
    				else if(s[i]=='1')y=i;
    			}
    			if(y-x>1)cout<<1<<endl;
    			else if(len<4)cout<<-1<<endl;
    			else if(len>4)cout<<2<<endl;
    			else {
    				if(x==1)cout<<3<<endl;
    				else cout<<2<<endl;
    			} 
    		}
    

组队赛1

本次组队赛是我和队友三个人在机场开往火车站的大巴上完成的,从开赛后两个半小时开始,ac了5道水题,之后会进行补题,补题报告写到下一周总结

自学/讲课

  • 自学了一些博弈论的知识,sg函数,还没有整理

  • 学习了一些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;
    }
    
  • 概率dp : 顾名思义,概率DP就是动态规划求概率的问题。一般来说,我们将dp数组存放的数据定义为到达此状态的概率,那么我们初值设置就是所有初始状态概率为1,最终答案就是终末状态dp值了。

    我们在进行状态转移时,是从初始状态向终末状态顺推,转移方程中大致思路是按照当前状态去往不同状态的位置概率转移更新DP,且大部分是加法。

  • 学习了倍增思想,总结了一些代码:

    // ST表
    const int maxn = 100000;
    int ST[maxn][22];
    int n;
    void build()
    {
        for (int j = 1; j <= 21;j++)
        {
            for (int i = 1; i + (1 << j) - 1 <= n;i++)
                ST[i][j] = max(ST[i][j - 1], ST[i + (1 << (j - 1))][j - 1]);
        }
    }
    int query(int l,int r)
    {
        int s = (int)log2(r - l + 1);
        return max(ST[l][s], ST[r - (1 << l) + 1][s]);
    }
    int main()
    {
        cin >> n;
        for (int i = 1; i <= n;i++)
            cin >> ST[0][i];
    }
    
    // LCA
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 500050;
    typedef long long ll;
    int fa[maxn][40], d[maxn], head[maxn];
    int lg[maxn];
    int n, m, s;
    int cnt;
    struct node
    {
     int nex, t;
    } e[maxn*2];
    
    void add(int x, int y)
    {
     e[++cnt].nex = head[x];
     e[cnt].t = y;
     head[x] = cnt;
    }
    
    void dfs(int f, int fath) // f表示当前节点,fath表示它的父亲节点
    {
     d[f] = d[fath] + 1;
     fa[f][0] = fath;
     for (int i = 1; (1 << i) <= d[f]; i++)
      fa[f][i] = fa[fa[f][i - 1]][i - 1]; //这个转移可以说是算法的核心之一
               //意思是f的2^i祖先等于f的2^(i-1)祖先的2^(i-1)祖先
               // 2^i=2^(i-1)+2^(i-1)
     for (int i = head[f]; i; i = e[i].nex)
      if (e[i].t != fath)
       dfs(e[i].t, f);
    }
    
    int lca(int x, int y)
    {
     if (d[x] < d[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
      swap(x, y);
     while (d[x] > d[y])
      x = fa[x][lg[d[x] - d[y]] - 1]; //先跳到同一深度
     if (x == y)         //如果x是y的祖先,那他们的LCA肯定就是x了
      return x;
     for (int k = lg[d[x]] - 1; k >= 0; k--) //不断向上跳(lg就是之前说的常数优化)
      if (fa[x][k] != fa[y][k])    //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
       x = fa[x][k], y = fa[y][k];
     return fa[x][0]; //返回父节点
    }
    
    int main()
    {
     cin >> n >> m >> s;
     for (int i = 1; i <= n - 1; i++)
     {
      int x, y;
      cin >> x >> y;
      add(x, y);
      add(y, x);
     }
     for (int i = 1; i <= n; i++)       //预先算出log_2(i)+1的值,用的时候直接调用就可以了
      lg[i] = lg[i - 1] + (1 << lg[i - 1] == i); //看不懂的可以手推一下
     dfs(s, s);
     for (int i = 1; i <= m; i++)
     {
      int x, y;
      cin >> x >> y;
      cout << lca(x, y) << endl;
     }
     system("pause");
    }
    
posted @ 2023-07-20 21:37  marti88414  阅读(7)  评论(0编辑  收藏  举报