大一寒假训练

期末考试加军训,大概一个月没碰了

现在寒假开启了,开始训练

集训前有三个星期,就刷cf题吧

目标不高,25道比自己水平高一些的题目

考前两三天可以练一练模板

搞起。

 

1.30(Tire树)

做了D. Vasiliy's Multiset

独立思考了挺久没做出来

看题解发现要用字典树

那就复习一波字典树

就是这样,通过题目来巩固相关知识点

 

Tire树模板

理解为字符前缀树更好一些

可以快速处理字符的前缀问题

注意根节点是0

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1e3 + 10;
int tire[MAXN][30], n, cnt;
bool End[MAXN];

void add(char *str)
{
	int len = strlen(str), p = 0;
	REP(i, 0, len)
	{
		int id = str[i] - 'a';
		if(!tire[p][id]) tire[p][id] = ++cnt; 
		p = tire[p][id];
	}
	End[p] = true;
}

bool search(char *str)
{
	int len = strlen(str), p = 0;
	REP(i, 0, len)
	{
		int id = str[i] - 'a';
		if(!tire[p][id]) return false;
		p = tire[p][id];
	}
	return End[p];
}

int main()
{
	char str[100];
	scanf("%d", &n);
	_for(i, 1, n)
	{
		scanf("%s", str);
		add(str);	
	} 
	
	while(~scanf("%s", str))
	{
		if(search(str)) puts("Yes");
		else puts("No");
	}
	
	return 0;
}

 

hdu 1251

这题的输入方式比较奇葩

gets遇到回车就停,所以要判断一下空串

用建立tire树后树形dp一次就ok了

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1e6 + 10;
int tire[MAXN][30], End[MAXN], ans[MAXN], cnt;

int dfs(int x)
{
	int res = End[x];
	_for(i, 0, 25)
		if(tire[x][i])
			res += dfs(tire[x][i]);
	return ans[x] = res;
}

void add(char *str)
{
	int len = strlen(str), p = 0;
	REP(i, 0, len)
	{
		int id = str[i] - 'a';
		if(!tire[p][id]) tire[p][id] = ++cnt;
		p = tire[p][id];
	}
	End[p]++;
}

int search(char *str)
{
	int len = strlen(str), p = 0;
	REP(i, 0, len)
	{
		int id = str[i] - 'a';
		if(!tire[p][id]) return 0;
		p = tire[p][id];
	}
	return ans[p];
}

int main()
{
	char str[100];
	while(gets(str) && *str) add(str);

	dfs(0);
	while(~scanf("%s", str))
		printf("%d\n", search(str));

	return 0;
}

 

poj 3630

比较裸的一道题

用字典树处理字符集前缀问题

注意开数组的时候是单词数乘上每个单词的长度

不要只开单词数

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1e5 + 10;
int tire[MAXN][15], n, cnt, ans;
bool End[MAXN];

void add(char *str)
{
	int len = strlen(str), p = 0, flag = 1, flag2 = 1;
	REP(i, 0, len)
	{
		int id = str[i] - '0';
		if(!tire[p][id]) tire[p][id] = ++cnt, flag = 0;
		p = tire[p][id];
		if(End[p]) flag2 = 0;
	}
	if(flag || !flag2) ans = false;
	End[p] = true;
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		cnt = 0; ans = true;
		memset(tire, 0, sizeof(tire));
		memset(End, false, sizeof(End));
		
		scanf("%d", &n);
		_for(i, 1, n)
		{
			char str[15];
			scanf("%s", str);
			add(str);
		}
		
		if(ans) puts("YES");
		else puts("NO");
	}

	return 0;
}

「一本通 2.3 例 2」The XOR Largest Pair

字典树还可以用来求异或最大值

用到了一些位运算

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1e5 * 33;
int tire[MAXN][2], p, cnt, n;

void add(int x)
{
	int p = 0;
	for(int i = 30; i >= 0; i--)
	{
		int id = (x >> i) & 1;
		if(!tire[p][id]) tire[p][id] = ++cnt;
		p = tire[p][id];
	}
}

int work(int x)
{
	int p = 0, res = 0;
	for(int i = 30; i >= 0; i--)
	{
		int id = (x >> i) & 1;
		if(tire[p][id ^ 1]) 
		{
			res |= (1 << i);
			p = tire[p][id ^ 1];
		}
		else p = tire[p][id];
	}
	return res;
}

int main()
{
	int ans = 0;
	scanf("%d", &n);
	_for(i, 1, n)
	{
		int x; scanf("%d", &x);
		add(x);
		ans = max(ans, work(x));
	}
	printf("%d\n", ans);
	return 0;
}

CF706D Vasiliy's Multiset

现在回到原来的题

其实学完前面的,这题不难的

其实就是上一题多了一个删除操作而已

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 2e5 * 33;
int tire[MAXN][2], vis[MAXN], cnt, q; 

void add(int x)
{
	int p = 0;
	for(int i = 30; i >= 0; i--)
	{
		int id = (x >> i) & 1;
		if(!tire[p][id]) tire[p][id] = ++cnt;
		p = tire[p][id];
		vis[p]++;
	}
}       

void erase(int x)
{
	int p = 0;
	for(int i = 30; i >= 0; i--)
	{
		int id = (x >> i) & 1;
		p = tire[p][id];
		vis[p]--;
	}
}   

int work(int x)
{
	int p = 0, res = 0;
	for(int i = 30; i >= 0; i--)
	{
		int id = (x >> i) & 1;
		if(tire[p][id ^ 1] && vis[tire[p][id ^ 1]]) 
		{
			res |= (1 << i);
			p = tire[p][id ^ 1];
		}
		else p = tire[p][id];
	}
	return max(x, res);
}        

int main()
{
	scanf("%d", &q);
	while(q--)
	{
		char op[5]; int x;
		scanf("%s%d", op, &x);
		if(op[0] == '+') add(x);
		if(op[0] == '-') erase(x);
		if(op[0] == '?') printf("%d\n", work(x));
	}
	return 0;
}

CF706C Hard problem

中途去做了一道有点水的dp题

我用的字符数组做的,一些操作挺麻烦的

用C++的string做很多操作会好写很多

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MAXN = 1e5 + 10;
char now[MAXN], pre1[MAXN], pre2[MAXN], t1[MAXN], t2[MAXN];
ll dp[MAXN][2];
int c[MAXN], n; 

void copy(char a[], char b[])
{
	memset(a, 0, sizeof(a));
	strcpy(a, b);
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n) scanf("%d", &c[i]);
	_for(i, 1, n)
	{
		scanf("%s", now); 
		copy(t1, now);
		reverse(now, now + strlen(now));
		copy(t2, now);
		
		dp[i][0] = dp[i][1] = 1e18;
		if(strcmp(pre1, t1) <= 0) dp[i][0] = min(dp[i][0], dp[i-1][0]);
		if(strcmp(pre2, t1) <= 0) dp[i][0] = min(dp[i][0], dp[i-1][1]);
		if(strcmp(pre1, t2) <= 0) dp[i][1] = min(dp[i][1], dp[i-1][0] + c[i]);
		if(strcmp(pre2, t2) <= 0) dp[i][1] = min(dp[i][1], dp[i-1][1] + c[i]);

		copy(pre1, t1);
		copy(pre2, t2);
	}

	printf("%lld\n", min(dp[n][0], dp[n][1]) == 1e18 ? -1 : min(dp[n][0], dp[n][1]));
	
	return 0;
}

用string写了一遍

可以直接赋值直接比较,可以用加法串联,swap等等

但是要用cin,要解绑才不会效率低

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MAXN = 1e5 + 10;
string s[MAXN][2];
ll dp[MAXN][2];
int c[MAXN], n; 

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin >> n;
	_for(i, 1, n) cin >> c[i];
	_for(i, 1, n)
	{
		cin >> s[i][0];
		s[i][1] = s[i][0];
		reverse(s[i][1].begin(), s[i][1].end());
		
		dp[i][0] = dp[i][1] = 1e18;
		if(s[i-1][0] <= s[i][0]) dp[i][0] = min(dp[i][0], dp[i-1][0]);
		if(s[i-1][1] <= s[i][0]) dp[i][0] = min(dp[i][0], dp[i-1][1]);
		if(s[i-1][0] <= s[i][1]) dp[i][1] = min(dp[i][1], dp[i-1][0] + c[i]);
		if(s[i-1][1] <= s[i][1]) dp[i][1] = min(dp[i][1], dp[i-1][1] + c[i]);
	}

	if(min(dp[n][0], dp[n][1]) == 1e18) cout << "-1" << endl;
	else cout << min(dp[n][0], dp[n][1]) << endl;
	
	return 0;
}

 

poj 3764

想半天不知道图上路径怎么处理

看题解发现要用lca的思想

把路径转化为两个点到根节点,这样就变成上一道题了

这个真忘记了,没办法,有时候一直卡住没有思路是因为需要用到的算法是知识盲区

lca我又已经忘记了

那就明天开始复习lca吧

反正现在就做题,哪里不会补哪里

通过题目来巩固知识点

现在Tire树这个知识点我已经掌握了

 

感觉还有好多算法我要补

二分图,状压dp,lca,kmp……

慢慢来吧

 

1.31(LCA)

继续学算法吧 今天复习LCA

买了一本书 信息学奥赛提高篇

下学期可以重点吃透这本书

加油

 

「一本通 4.4 例 1」点的距离(模板)

学习新算法一定要深刻理解原理,自己推一遍

这样子代码就很容易写出

以后也不会忘记

这个学习方法非常重要

lca无非就是树上倍增

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e5 + 10;
const int MAXM = 20;
int up[MAXN][MAXM], d[MAXN], n, q;
vector<int> g[MAXN];

void dfs(int u, int fa)
{
	for(auto v: g[u])
	{
		if(v == fa) continue;
		d[v] = d[u] + 1;
		up[v][0] = u;
		dfs(v, u);
	}
}

void init()
{
	dfs(1, -1);
	REP(j, 1, MAXM)
		_for(i, 1, n)
			up[i][j] = up[up[i][j-1]][j-1];
}

int lca(int a, int b)
{
	if(d[a] < d[b]) swap(a, b);
	for(int j = MAXM - 1; j >= 0; j--)
		if(d[up[a][j]] >= d[b])
			a = up[a][j];
	if(a == b) return a;
	for(int j = MAXM - 1; j >= 0; j--)
		if(up[a][j] != up[b][j])
			a = up[a][j], b = up[b][j];
	return up[a][0];
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n - 1)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	init();
	scanf("%d", &q);
	while(q--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		printf("%d\n", d[u] + d[v] - 2 * d[lca(u, v)]);
	}
	
	return 0;
}

 

poj 2763(LCA+树状数组+dfs序+差分)

这题像是很多题拼在一起的

比较套路

涉及到挺多知识点,要都很熟练才能做出来

 

首先是求两点之间距离就要用lca转化到点到根节点的距离

但是这题多了一点,边的长度会变化

也就是每点到根的距离会变化

一次变化就会影响一颗子树的值,一颗子树的每一个值都会加上或者减去一个数

然后每次询问一个点的值

用dfs序把节点拍到一个线性数组上,然后支持动态区间修改,询问单点吗

所以就是树状数组加一维差分。树状数组本来可以支持单点修改,区间查询,用个差分转化一下就可以支持区间修改,单点查询。

 

我写完后一直卡在一个点上很久

源于我对dfs序运用的不熟练

初始化dfs序后,要操作节点的时候,一定要用它的序号,而不是这个点本身

也就是说要用L[u]而不是u

还是蛮有收获的

复习了挺多知识点

#include<cstdio>
#include<vector>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e5 + 10;
const int MAXM = 20;
int up[MAXN][MAXM], f[MAXN], d[MAXN], D[MAXN];
int L[MAXN], R[MAXN], cnt, n, q, s;
int u[MAXN], v[MAXN], w[MAXN];
struct node 
{ 
	int v, w; 
	node(int v, int w) : v(v), w(w) {}
};
vector<node> g[MAXN];

int lowbit(int x) { return x & (-x); }

void add(int x, int p)
{
	for(; x <= n; x += lowbit(x))
		f[x] += p;
}

int sum(int x)
{
	int res = 0;
	for(; x; x -= lowbit(x))
		res += f[x];
	return res;
}

void Add(int l, int r, int p)
{
	add(l, p); add(r + 1, -p);
}

void dfs(int u, int fa)
{
	L[u] = ++cnt;
	REP(i, 0, g[u].size())
	{
		node x = g[u][i];
		int V = x.v;
		if(V == fa) continue;
		D[V] = D[u] + x.w;
		d[V] = d[u] + 1;
		up[V][0] = u;
		Add(cnt + 1, cnt + 1, D[V]); //cnt + 1是V的序号。这里区间缩为1个点,照常操作 
		dfs(V, u);
	}
	R[u] = cnt;
}

void init()
{
	dfs(1, -1);
	REP(j, 1, MAXM)
		_for(i, 1, n)
			up[i][j] = up[up[i][j-1]][j-1];
}

int lca(int a, int b)
{
	if(d[a] < d[b]) swap(a, b);
	for(int j = MAXM - 1; j >= 0; j--)
		if(d[up[a][j]] >= d[b])
			a = up[a][j];
	if(a == b) return a;
	for(int j = MAXM - 1; j >= 0; j--)
		if(up[a][j] != up[b][j])
			a = up[a][j], b = up[b][j];
	return up[a][0];
}

int main()
{
	scanf("%d%d%d", &n, &q, &s);
	_for(i, 1, n - 1)
	{
		scanf("%d%d%d", &u[i], &v[i], &w[i]);
		g[u[i]].push_back(node(v[i], w[i]));
		g[v[i]].push_back(node(u[i], w[i]));
	}
	
	init();
	while(q--)
	{
		int op, x, y;
		scanf("%d", &op);
		if(op == 0)
		{
			scanf("%d", &x);
			printf("%d\n", sum(L[s]) + sum(L[x]) - 2 * sum(L[lca(s, x)])); //用了dfs序列初始化后,后面处理的时候要用L[]而不是用点本身 。血的教训 
			s = x;
		}
		else
		{
			scanf("%d%d", &x, &y);
			int t = d[u[x]] > d[v[x]] ? u[x] : v[x];
			Add(L[t], R[t], -w[x] + y);
			w[x] = y;
		}
	}

	return 0;
}

在做一道lca的题遇到了树上差分

干脆顺便把各种差分都复习一遍

搞起

差分数组 and 树上差分这个写得相当好,我就是看这个学差分的

 

P1083 [NOIP2012 提高组] 借教室(二分+一维差分)

水题

差分使用于离线的区间修改问题

如果要在线就结合树状数组

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e6 + 10;
int r[MAXN], d[MAXN], s[MAXN], t[MAXN], n, m;
int a[MAXN];

void add(int l, int r, int w)
{
	a[l] += w; a[r + 1] -= w;
}

bool check(int key)
{
	_for(i, 1, n) a[i] = r[i] - r[i-1];
	_for(i, 1, key) add(s[i], t[i], -d[i]);
	_for(i, 1, n)
	{
		a[i] += a[i-1];
		if(a[i] < 0) return false;
	}
	return true;
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n) scanf("%d", &r[i]);
	_for(i, 1, m) scanf("%d%d%d", &d[i], &s[i], &t[i]);
	
	int l = 0, r = m + 1;
	while(l + 1 < r)
	{
		int mid = (l + r) >> 1;
		if(check(mid)) l = mid;
		else r = mid;
	}
	
	if(r == m + 1) puts("0");
	else printf("-1\n%d\n", r);

	return 0;
}

树上差分原理

可以用来解决一个段路径上点或边经过多少次的问题

可以从最后统计方式反推,最后是子树和

点差分的话就是d[u]++ d[v]++ d[lca(u, v)]-- d[fa[lca(u, v)]]--

边差分的话就d[u]++ d[v]++ d[lca(u, v)] -= 2

不是按照一次,而是加减某个值,就可以看作经过很多次,一样的

P3128 [USACO15DEC]Max Flow P(树上差分模板)

模板题

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 5e4 + 10;
const int MAXM = 20;
int up[MAXN][MAXM], d[MAXN], a[MAXN], n, m, ans;
vector<int> g[MAXN];

void dfs(int u, int fa)
{
	for(auto v: g[u])
	{
		if(v == fa) continue;
		d[v] = d[u] + 1;
		up[v][0] = u;
		dfs(v, u);
	}
}

void init()
{
	dfs(1, 0);
	REP(j, 1, MAXM)
		_for(i, 1, n)
			up[i][j] = up[up[i][j-1]][j-1];
}

int lca(int a, int b)
{
	if(d[a] < d[b]) swap(a, b);
	for(int j = MAXM - 1; j >= 0; j--)
		if(d[up[a][j]] >= d[b])
			a = up[a][j];
	if(a == b) return a;
	for(int j = MAXM - 1; j >= 0; j--)
		if(up[a][j] != up[b][j])
			a = up[a][j], b = up[b][j];
	return up[a][0];
}

void sum(int u, int fa)
{
	for(auto v: g[u])
	{
		if(v == fa) continue;
		sum(v, u); //先往下搜,回溯的时候再统计。这里不要写成dfs,写sum 
		a[u] += a[v];
	}
	ans = max(ans, a[u]);
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n - 1)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	init();
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		a[u]++; a[v]++; a[lca(u, v)]--; a[up[lca(u, v)][0]]--;
	}
	sum(1, 0);
	printf("%d\n", ans);

	return 0;
}

2.1(树上差分)

今天继续复习树上差分

真的隔了高三一年,很多很多之前的东西全都忘记了

现在都捡起来

P3258 [JLOI2014]松鼠的新家(树上点差分+小细节)

竟然发现之前我写的模板是错的

我的那种写法,根节点和根节点的父亲深度是一样的

所以我现在换了一种写法,是对的

注意根节点是1,根节点的父亲是0

这两个深度不同,要小心

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 3e5 + 10;
const int MAXM = 20;
int up[MAXN][MAXM], b[MAXN], a[MAXN], d[MAXN], n;
vector<int> g[MAXN];

void dfs(int u, int fa)
{
	up[u][0] = fa; d[u] = d[fa] + 1;
	REP(j, 1, MAXM) up[u][j] = up[up[u][j-1]][j-1];
	
	for(auto v: g[u])
	{
		if(v == fa) continue;
		dfs(v, u);
	}
}

void sum(int u, int fa)
{
	for(auto v: g[u])
	{
		if(v == fa) continue;
		sum(v, u);
		a[u] += a[v];
	}
}

int lca(int a, int b)
{
	if(d[a] < d[b]) swap(a, b);
	for(int j = MAXM - 1; j >= 0; j--)
		if(d[up[a][j]] >= d[b])
			a = up[a][j];
	if(a == b) return a;
	for(int j = MAXM - 1; j >= 0; j--)
		if(up[a][j] != up[b][j])
			a = up[a][j], b = up[b][j];
	return up[a][0];
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n) scanf("%d", &b[i]);
	_for(i, 1, n - 1)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	dfs(1, 0);
	_for(i, 2, n)
	{
		int u = b[i], v = b[i-1];
		a[u]++; a[v]++;
		a[lca(u, v)]--; a[up[lca(u, v)][0]]--; 
	}
	sum(1, 0);
	_for(i, 2, n) a[b[i]]--;
	_for(i, 1, n) printf("%d\n", a[i]);
	
	return 0;
}

POJ 3417(思维+树上边差分)

这题主要难在思维上

一开始关注点在主要边,没想出来

后来我看了题解,主要关注点在附加边

切成多个联通分量的关键点在于环,多一条附加边就会成环

其实是我图论题做的少,对这个不熟悉,没有经验

一条附加边就会构成一个环,路径上的每条边要加1

最后如果边值为0,就有m个方案

为1就有1个方案

大于1就不可能

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1e5 + 10;
const int MAXM = 20;
int up[MAXN][MAXM], a[MAXN], d[MAXN], n, m;
vector<int> g[MAXN];

void dfs(int u, int fa)
{
	up[u][0] = fa; d[u] = d[fa] + 1;
	REP(j, 1, MAXM) up[u][j] = up[up[u][j-1]][j-1];
	
	for(auto v: g[u])
	{
		if(v == fa) continue;
		dfs(v, u);
	}
}

void sum(int u, int fa)
{
	for(auto v: g[u])
	{
		if(v == fa) continue;
		sum(v, u);
		a[u] += a[v];
	}
}

int lca(int a, int b)
{
	if(d[a] < d[b]) swap(a, b);
	for(int j = MAXM - 1; j >= 0; j--)
		if(d[up[a][j]] >= d[b])
			a = up[a][j];
	if(a == b) return a;
	for(int j = MAXM - 1; j >= 0; j--)
		if(up[a][j] != up[b][j])
			a = up[a][j], b = up[b][j];
	return up[a][0];
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n - 1)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	dfs(1, 0);
	_for(i, 1, m)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		a[u]++; a[v]++; a[lca(u, v)] -= 2;
	}
	sum(1, 0);
	
	int ans = 0;
	_for(i, 2, n)
	{
		if(a[i] == 1) ans++;
		if(a[i] == 0) ans += m;
	}
	printf("%d\n", ans);
	
	return 0;
}

 

P2680 [NOIP2015 提高组] 运输计划(二分+LCA+树上边差分)

独立做出紫题,爽

还是想了蛮久的

首先我们所要求的答案时间显然是单调性的,也就是说这个时间满足,大于这个时间肯定也满足

所以可以用二分答案

那么对于一个给出的时间

就有一些路径是超过这些时间的

那么就求这些路径共同覆盖的边中最大的,然后减去,看能否满足就行

求路径长度要lca,多条路径共同覆盖的边也要用lca树上边差分

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 3e5 + 10;
const int MAXM = 20;
int up[MAXN][MAXM], d[MAXN], D[MAXN], a[MAXN];
int ti[MAXN], n, m, ma;

struct node
{
	int u, v, len;
}r[MAXN];
struct node2 { int v, w; };
vector<node2> g[MAXN];

bool cmp(node a, node b)
{
	return a.len > b.len;
}

void dfs(int u, int fa)
{
	up[u][0] = fa; d[u] = d[fa] + 1;
	REP(j, 1, MAXM) up[u][j] = up[up[u][j-1]][j-1];
	
	for(auto x: g[u])
	{
		if(x.v == fa) continue;
		D[x.v] = D[u] + x.w;
		ti[x.v] = x.w;
		dfs(x.v, u);
	}
}

int lca(int a, int b)
{
	if(d[a] < d[b]) swap(a, b);
	for(int j = MAXM - 1; j >= 0; j--)
		if(d[up[a][j]] >= d[b])
			a = up[a][j];
	if(a == b) return a;
	for(int j = MAXM - 1; j >= 0; j--)
		if(up[a][j] != up[b][j])
			a = up[a][j], b = up[b][j];
	return up[a][0];
}

void sum(int u, int fa, int t)
{
	for(auto x: g[u])
	{
		if(x.v == fa) continue;
		sum(x.v, u, t);
		a[u] += a[x.v];
	}
	if(a[u] == t - 1 && u != 1) ma = max(ma, ti[u]);
}

bool check(int key)
{
	if(key >= r[1].len) return true;
	int t = lower_bound(r + 1, r + m + 1, node{0, 0, key}, cmp) - r; //upper和lower区别在于相等时怎么处理,这里要思考一下 
	
	memset(a, 0, sizeof(a));
	REP(i, 1, t)
	{
		int u = r[i].u, v = r[i].v;
		a[u]++; a[v]++; a[lca(u, v)] -= 2;
	}
	ma = 0;
	sum(1, 0, t);
	return r[1].len - ma <= key;
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n - 1)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		g[u].push_back(node2{v, w});
		g[v].push_back(node2{u, w});
	}
	
	dfs(1, 0);
	_for(i, 1, m)
	{
		scanf("%d%d", &r[i].u, &r[i].v);
		r[i].len = D[r[i].u] + D[r[i].v] - 2 * D[lca(r[i].u, r[i].v)];
	}
	sort(r + 1, r + m + 1, cmp);
	
	int l = -1, r = 1e9;
	while(l + 1 < r)
	{
		int mid = (l + r) >> 1;
		if(check(mid)) r = mid;
		else l = mid;
	}
	printf("%d\n", r);
	
	return 0;
}

一定要注意学习方法的问题

做题目独立思考,至少思考一个小时

不要轻易看题解和代码

 

2.2(KMP)

kmp算法还是有点难理解了

我花了一个上午认真看了几篇博客算是彻底理解了,自己能讲出来来龙去脉

P3375 【模板】KMP字符串匹配

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e6 + 10;
char a[MAXN], b[MAXN];
int Next[MAXN], lena, lenb;

void get_next()
{
	Next[0] = -1;
	int i = 0, j = -1;
	while(i < lenb)
	{
		if(j == -1 || b[i] == b[j])
		{
			i++; j++;
			Next[i] = j;
		}
		else j = Next[j];
	}
}

void kmp()
{
	int i = 0, j = 0;
	while(i < lena)
	{
		if(j == -1 || a[i] == b[j])
		{
			i++; j++;
			if(j == lenb) printf("%d\n", i - j + 1);
		}
		else j = Next[j];
	}
}

int main()
{
	scanf("%s%s", a, b);
	lena = strlen(a), lenb = strlen(b);
	get_next();
	kmp();
	REP(i, 0, lenb) printf("%d ", Next[i+1]); puts("");
	return 0;
}

 

用next数组可以有一个重复子串的结论

len % (len - next[len +1]) == 0时为重复子串,比如abcabcabc

可以自己用笔画一下

一段长度为len - next[len +1]

CF1200E Compress Words

一开始理解错意思,以为只有前后两个合并

原来是合并的结果和新的一个合并,卡了好久

考的就是kmp的合并过程

学算法一定要深刻理解原理,真正的题目不会是模板,而是变形,那就要靠对算法本身的理解

这道题还是学到一些东西的

(1)string截取。字符串的题目用string很方便,字符长度不定,连接直接加,复制直接赋值

这里学到了截取的操作

s.substr(pos) 表示从pos到结尾的一段

s.substr(pos, n)表示从pos开始,长度为n的一段

(2)kmp的一些细节。这道题要求模式串匹配完了之后继续往后

其实next数组是有求到next[len]的,那样就照常往下匹配好了

加一句j != lenb

(3)交上去超时了。最后匹配的时候只用看最后一段就好了,前面其实是没有用的

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e6 + 10;
string a, b;
int Next[MAXN], lena, lenb, n;

void get_next()
{
	Next[0] = -1;
	int i = 0, j = -1;
	while(i < lenb)
	{
		if(j == -1 || b[i] == b[j])
		{
			i++; j++;
			Next[i] = j;
		}
		else j = Next[j];
	}
}

int kmp()
{
	get_next();
	int i = lena - lenb, j = 0;
	while(i < lena)
	{
		if(j != lenb && (j == -1 || a[i] == b[j])) i++, j++;
		else j = Next[j];
	}
	return j - 1;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin >> n >> a;
	_for(i, 2, n)
	{
		cin >> b;
		lena = a.length(), lenb = b.length();
		a += b.substr(kmp() + 1);
	}
	cout << a << endl;
	
	return 0;
}

P4824 [USACO15FEB]Censoring S(分类讨论)

开始用kmp暴力删除元素。同时主串不用从头开始匹配,有些地方已经匹配过不可能了。交上去T了2个点,主要是子串很小的时候要删除很多次,一次删除是O(n)

于是我就在子串比较小的时候写了另外一种算法,用一个栈暴力,这个时候就是之前校赛的一道题了,所以我很快就想到了方法,子串很小的时候可以这么做

交上去AC了

不过我觉得这应该不是正解,我这是分两种情况写的

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e6 + 10;
int Next[MAXN], lena, lenb, st;
string a, b;

void get_next()
{
	Next[0] = -1;
	int i = 0, j = -1;
	while(i < lenb)
	{
		if(j == -1 || b[i] == b[j])
		{
			i++; j++;
			Next[i] = j;
		}
		else j = Next[j];
	}
}

bool kmp()
{
	int i = st, j = 0;
	while(i < lena && j < lenb)
	{
		if(j == -1 || a[i] == b[j]) i++, j++;
		else j = Next[j];
	}
	if(j == lenb)
	{
		a = a.substr(0, i - j) + a.substr(i);
		st = max(i - j - lenb + 1, 0);
		lena -= lenb;
		return true;
	}
	return false;
}

char s[MAXN];
int p;

bool check()
{
	if(p < lenb) return false;
	_for(i, p - lenb + 1, p)
		if(s[i] != b[i - (p - lenb + 1)])
			return false;
	return true;
}

void solve()
{
	REP(i, 0, lena)
	{
		s[++p] = a[i];
		if(check()) p -= lenb;
	}
	_for(i, 1, p) cout << s[i];
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin >> a >> b;
	lena = a.length(), lenb = b.length();
	st = 0;
	
	if(lenb <= 100)
	{
		solve();
		return 0;
	}
	
	get_next();
	while(kmp());
	cout << a << endl;
	
	return 0;
}

 

瞟了一眼正解,是结合我两种方法的优点,也就是kmp+栈

明天写写吧。

 

2.3(KMP)

P4824 [USACO15FEB]Censoring S(kmp+栈)

利用kmp滑动模式串的性质,当一个匹配成功后,不用从头开始

要对kmp有深刻的理解

我发现一开始求next数组时j=-1是为了前后缀不等于整个串。j一直会比i小,因为i,j同时加,j有时还会等于next[j]而倒推

如果i=j的话就是全串了。总之一开始设j为-1,然后改该怎么跑怎么跑

这道题关键就是模仿next数组求一个数组可以往前滑动

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e6 + 10;
int Next[MAXN], k[MAXN], lena, lenb, p, sti, stj;
string a, b;
char s[MAXN];

void get_next()
{
	Next[0] = -1;
	int i = 0, j = -1;
	while(i < lenb)
	{
		if(j == -1 || b[i] == b[j])
		{
			i++; j++;
			Next[i] = j;
		}
		else j = Next[j];
	}
}

void kmp()
{
	int i = sti, j = stj;
	while(i <= p && j < lenb)
	{
		if(j == -1 || s[i] == b[j]) 
		{
			k[i] = j; 
			i++, j++;
		}
		else j = Next[j];
	}
	
	sti = i, stj = j;
	if(j == lenb)
	{
		p -= lenb;
		sti = i - j;
		stj = k[i - j - 1] + 1;
	}
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin >> a >> b;
	lena = a.length(), lenb = b.length();
	sti = 1; stj = 0;

	get_next();
	REP(i, 0, lena)
	{
		s[++p] = a[i];
		kmp();
	}
	_for(i, 1, p) cout << s[i];
	
	return 0;
}

 

《信息学奥赛一本通》提高版题单

这个题单很不错耶

立个flag,下个学期刷完这份题单。大一下的目标就是这个

做到大部分题目都独立做出

 

「一本通 2.2 例 1」剪花布条(kmp细节修改)

注意统计的是不重叠的子串。所以每次结束后要j设为0

深刻理解kmp,修改一些细节即可

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e3 + 10;
int Next[MAXN], lena, lenb;
string a, b;

void get_next()
{
	Next[0] = -1;
	int i = 0, j = -1;
	while(i < lenb)
	{
		if(j == -1 || b[i] == b[j])
		{
			i++; j++;
			Next[i] = j;
		}
		else j = Next[j];
	}
}

int work()
{
	lena = a.length(), lenb = b.length();
	get_next();
	
	int i = 0, j = 0, res = 0;
	while(i < lena)
	{
		if(j == -1 || a[i] == b[j]) i++, j++;
		else j = Next[j];
		if(j == lenb) res++, j = 0;
	}
	return res;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	while(1)
	{
		cin >> a;
		if(a[0] == '#' && a.length() == 1) break;
		cin >> b;
		cout << work() << endl;
	}
	
	return 0;
}

「一本通 2.1 练习 1」Power Strings(重复子串结论)

用到了前面说的那个结论

注意一定要能整除才行

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e6 + 10;
int Next[MAXN], lena;
string a;

void get_next()
{
	Next[0] = -1;
	int i = 0, j = -1;
	while(i < lena)
	{
		if(j == -1 || a[i] == a[j])
		{
			i++; j++;
			Next[i] = j;
		}
		else j = Next[j];
	}
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	while(1)
	{
		cin >> a; 
		if(a == ".") break;
		lena = a.length(); get_next();
		if(lena % (lena - Next[lena]) == 0) cout << lena / (lena - Next[lena]) << endl;
		else cout << "1" << endl;
	}
	
	return 0;
}

「一本通 2.2 练习 1」Radio Transmission(重复子串变形)

和上一道题类似。不同的是重复的部分会多出来前面的一部分。这个时候就把这一部分删掉就行。

枚举删掉的部分就好

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e6 + 10;
int Next[MAXN], len;
string a;

void get_next()
{
	Next[0] = -1;
	int i = 0, j = -1;
	while(i < len)
	{
		if(j == -1 || a[i] == a[j])
		{
			i++; j++;
			Next[i] = j;
		}
		else j = Next[j];
	}
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin >> len >> a;
	get_next();
	
	for(int i = len; i >= 1; i--)
		if(i % (i - Next[i]) == 0)
		{
			cout << i - Next[i] << endl;
			break;
		}
	
	return 0;
}

2.4(KMP)

11点睡6点起+晨跑半小时,即使前一天很晚睡依然6点起

这个习惯真的太好了,每一天都精神饱满。

最近几天我训练量都挺大的,精力来源于这个习惯

这个寒假一定要坚持下去这个习惯,不管几点睡都第二天6点起+晨跑半小时。尽量11点前睡

 

 

今天看到了一篇文章

关于ACM转载很多的一篇文章和对dsh神牛的感想

总结几点

一.关于算法的学习。

算法一定要理解本质,来龙去脉。我之前有一个问题,就是一个算法学了一段时间之后就敲不出模板了

而最近我发现,如果我深刻理解了算法,能自己讲出来,把别人讲懂

那么即使过了很长时间,我依然记得算法的本质,就可以写出完整的代码

甚至写出来的代码每次都不完全一样,但都写出来了

实际做题很多时候是模板的变形,考你对算法本质的理解

我最近做kmp的题,就很多考的是kmp的思想。你需要深刻理解kmp才能写出来,只记模板是写不出来的

所以以后学算法一定要深刻理解本质,这样才不会遗忘,也能做出变形的题目

二.独立思考

这也强化了我之前的想法。我的OI生涯之所以结果不好,最重要的原因就是独立思考少,看题解太多,抄代码太多

这样麻痹自己好像学到了很多东西,其实效率很低

宁愿做题慢一些,想久一些,不深刻想一段时间绝不看题解

即使看题解也是理解思路,不会去看代码

三.热爱

内在驱动力才能走得更远

保持对acm的热爱,对解题的热爱,快乐地度过我的acm生涯

 

#10046. 「一本通 2.2 练习 2」OKR-Periods of Words(kmp变形)

这题是个好题,要深刻理解kmp才能做出

读题后发现需要一个数组,即前缀和后缀相等的最小长度

求出这个数组就完事

这个数组可以用next数组去求,用笔画一画。

一开始写完了T了两个点,后面记忆化了一下就ac了

好题好题

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

typedef long long ll;
const int MAXN = 1e6 + 10;
int Next[MAXN], k[MAXN], len;
string a;

void get_next()
{
	Next[0] = -1;
	int i = 0, j = -1;
	while(i < len)
	{
		if(j == -1 || a[i] == a[j])
		{
			i++; j++;
			Next[i] = j;
		}
		else j = Next[j];
	}
}

int dfs(int x)
{
	if(k[x]) return k[x];
	if(Next[x]) return k[x] = dfs(Next[x]);
	else return x;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin >> len >> a;
	get_next();
	
	ll ans = 0;
	_for(i, 1, len)
	{
		int t = dfs(i);
		if(t <= i / 2) ans += i - t;
	}
	cout << ans << endl;

	return 0;
}

「一本通 2.2 练习 3」似乎在梦中见过的样子(kmp变形)

想了个O(n^2)的算法,这个时间复杂度对于1.5e4来说很极限,想试一下能不能过,然后竟然过了,最慢0.4s

看来1e7到1e8都能过

 

其实思路并不难

首先是枚举每一个位置,即枚举左端点,我的实现方法是每次删除第一个字符来

对于当前字符串,做一个kmp

然后枚举右端点,就有一个子串

这道题也是用了Next数组的性质,跟上一道有点像

这里贪心一下,求出前后缀相同长度在大于等于k的情况下最小

就一直对本身Next就好,因为会重复计算,所以要记忆化一下

求出来后按照题目要求写个判断就好

至此,题单里面的kmp就全部刷完了,爽!!都是独立思考做出的!!

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1.5e4 + 10;
int Next[MAXN], f[MAXN], k, st, len, ans;
string a;

void get_next()
{
	Next[0] = -1;
	int i = 0, j = -1;
	while(i < len)
	{
		if(j == -1 || a[i] == a[j]) 
		{
			i++; j++;
			Next[i] = j;
		}
		else j = Next[j]; 
	}
}

int dfs(int x)
{
	if(f[x]) return f[x];
	if(Next[x] >= k) return f[x] = dfs(Next[x]);
	else return x;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin >> a >> k;
	while(1)
	{
		memset(f, 0, sizeof(f));
		len = a.length();
		get_next();
		
		_for(i, 1, len)
		{
			int t = dfs(i);
			if(t >= k && i >= 2 * t + 1) ans++;
		}
		if(len == 1) break;
		a = a.substr(1);
	}
	cout << ans << endl;

	return 0;
}

 

明天复习二分图

 

2.5(二分图)

首先学习二分图最大匹配,匈牙利算法

二分图最大匹配——匈牙利算法这个讲得很好

其实蛮简单的,本质就是一直递归下去看能不能腾出位置,这样就遍历了所有可能

比如a被占了,那么看占a的人能不能占其他的,同时a就在后面不能再访问了,那么占a的人又去找,这就是一样的过程,就是递归了

一直这样下去,如果找到了一个没匹配的,那么连锁反应前面所有都可以有新的匹配了

理解算法本质之后自己写出代码,不要去抄模板

记住理解思路后自己写代码,千万千万不要去抄代码

HDU 2063(二分图最大匹配模板)

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 500 + 10;
int vis[MAXN], link[MAXN], k, m, n;
vector<int> g[MAXN];

bool dfs(int u)
{
	for(auto v: g[u])
	{
		if(vis[v]) continue;
		vis[v] = 1;
		if(!link[v] || dfs(link[v]))
		{
			link[v] = u;
			return true;
		}
	}
	return false;
}

int main()
{
	while(scanf("%d", &k) && k)
	{
		scanf("%d%d", &m, &n);
		memset(link, 0, sizeof(link));
		_for(i, 1, m) g[i].clear();
		
		while(k--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			g[u].push_back(v);
		}	
	
		int ans = 0;
		_for(i, 1, m)
		{
			memset(vis, 0, sizeof(vis));
			if(dfs(i)) ans++;
		}
		printf("%d\n", ans);
	}
	return 0;
}

poj 2492(二分图判定染色法)

染色法挺简单的,一边为0,一边为1 如果遇到同种颜色就不是二分图

要理解思想,不要抄代码

理解思想后自己写代码实现

#include<cstdio>
#include<vector>
#include<cstring>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 2000 + 10;
int color[MAXN], vis[MAXN], n, m;
vector<int> g[MAXN];

bool dfs(int u)
{
	REP(i, 0, g[u].size())
	{
		int v = g[u][i];
		if(vis[v])
		{
			if(color[u] == color[v]) return false;
			continue;
		}
		vis[v] = 1;
		color[v] = color[u] ^ 1;
		if(!dfs(v)) return false;
	}
	return true;
}

bool check()
{
	_for(i, 1, n)
		if(!vis[i] && !dfs(i))
			return false;
	return true;
}

int main()
{
	int T; scanf("%d", &T);
	_for(kase, 1, T)
	{
		scanf("%d%d", &n, &m);
		_for(i, 1, n) g[i].clear();
		memset(color, 0, sizeof(color));
		memset(vis, 0, sizeof(vis));
		
		while(m--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			g[u].push_back(v);
			g[v].push_back(u);
		}
		
		if(!check()) printf("Scenario #%d:\nSuspicious bugs found!\n\n", kase);
		else printf("Scenario #%d:\nNo suspicious bugs found!\n\n", kase);
	}
	
	return 0;
}

poj 3041(最小覆盖点集=最大匹配)

这里用到了一个结论

最小覆盖点集=最大匹配

最小覆盖点集就是用选择最少的点,使得所有的边至少有一个端点被选中

即用最少的点去覆盖所有边

对于这道题,需要建图

(x, y)在x与y连一条边,二分图左边是行,右边是列

那就求最小覆盖点集就行了

这种坐标x与y坐标连接建立图我之前遇到过

这里的y不用加上n

记住算法原理,每次做题都重新根据原理写一遍,不要copy模板

#include<cstdio>
#include<vector>
#include<cstring>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 500 + 10;
int link[MAXN], vis[MAXN], n, k;
vector<int> g[MAXN];

bool dfs(int u)
{
	REP(i, 0, g[u].size())
	{
		 int v = g[u][i];
		 if(vis[v]) continue;
		 vis[v] = 1;
		 if(!link[v] || dfs(link[v]))
		 {
		 	link[v] = u;
		 	return true;
		 }
	}
	return false;
}

int main()
{
	scanf("%d%d", &n, &k);
	while(k--)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		g[x].push_back(y);
	}
	
	int ans = 0;
	_for(i, 1, n)
	{
		memset(vis, 0, sizeof(vis));
		if(dfs(i)) ans++;
	}
	printf("%d\n", ans);
	
	return 0;
}

poj 1325(最小覆盖点集)

匈牙利算法的时间复杂度是nm,点数乘边数

这道题可以转化成上一道题,一个任务看成一个点,一种模式看作可以干掉一行或者一列

#include<cstdio>
#include<vector>
#include<cstring>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 100 + 10;
int link[MAXN], vis[MAXN], n, m, k;
vector<int> g[MAXN];

bool dfs(int u)
{
	REP(i, 0, g[u].size())
	{
		int v = g[u][i];
		if(vis[v]) continue;
		vis[v] = 1;
		if(!link[v] || dfs(link[v]))
		{
			link[v] = u;
			return true;
		}
	}
	return false;
}

int main()
{
	while(scanf("%d", &n) && n)
	{
		memset(link, 0, sizeof(link));
		_for(i, 0, n - 1) g[i].clear();
		
		scanf("%d%d", &m, &k);
		while(k--)
		{
			int i, x, y;
			scanf("%d%d%d", &i, &x, &y);
			if(!x || !y) continue;
			g[x].push_back(y);
		}
		
		int ans = 0;
		_for(i, 1, n - 1)
		{
			memset(vis, 0, sizeof(vis));
			if(dfs(i)) ans++;
		}
		printf("%d\n", ans);
	}
	
	return 0;
}

poj 2226(最小覆盖点集+建图)

这题和3041很像

不同的是那道题一下可以干掉一行或者一列,这道题只能干掉连续的一行或一列

那就转化一下就好了,把这种情况区分一下

所以就给他们分配id就好了

#include<cstdio>
#include<vector>
#include<cstring>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 60;
int link[MAXN * MAXN], vis[MAXN * MAXN], n, m;
int x[MAXN][MAXN], y[MAXN][MAXN], idx, idy;
vector<int> g[MAXN * MAXN];
char s[MAXN][MAXN];

bool dfs(int u)
{
	REP(i, 0, g[u].size())
	{
		int v = g[u][i];
		if(vis[v]) continue;
		vis[v] = 1;
		if(!link[v] || dfs(link[v]))
		{
			link[v] = u;
			return true;
		}
	} 
	return false;
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n) scanf("%s", s[i] + 1);
	
	_for(i, 1, n)
		_for(j, 1, m)
			if(s[i][j] == '*')
			{
				int p = j; idx++;
				while(s[i][p + 1] == '*') p++;
				_for(k, j, p) x[i][k] = idx;
				j = p;
			}
			
	_for(j, 1, m)
		_for(i, 1, n)
			if(s[i][j] == '*')
			{
				int p = i; idy++;
				while(s[p + 1][j] == '*') p++;
				_for(k, i, p) y[k][j] = idy;
				i = p;
			}
	
	_for(i, 1, n)
		_for(j, 1, m)
			if(s[i][j] == '*')
				g[x[i][j]].push_back(y[i][j]);
	
	int ans = 0;
	_for(i, 1, idx)
	{
		memset(vis, 0, sizeof(vis));
		if(dfs(i)) ans++;
	}
	printf("%d\n", ans);
	
	return 0;
}

2.6(二分图)

最近有点疲惫,毕竟连续刚了这多天了

所以这几天做题量会少一些

poj 2724(最大匹配+建图)

这题真的做了我好久

怎么建图是关键

很容易想到只差一位的就连一条边

建立二分图的关键在于分成两部分,同一部分之间没有边相连

就是说同一部分不可能只差一位

如果只差一位,那么1的奇偶是不同的。也就是说,1的奇偶相同的时候不可能有边

所以可以这样把其分类

这样就可以把他们根据1的奇偶扔到两个数组,然后再连边

还有一种实现起来更简单的方法

就是两部分都是自己。因为自己和自己不可能连边

那么这个时候边也要翻倍了,也就是一个对称的图。

那么最大匹配也会翻倍,最后除以2就好了

然后提一下为什么是最大匹配

首先题目说不能覆盖,也就是不可能两条边连在一个点上。而二分图的匹配就是左边的一个点匹配右边的一个点,此外没有边连他们,符合这个特质

然后多一条边就可以少一次净化,所以边要最多,就是最大匹配

然后一定要注意判重,太坑了。判重可以用set,但是要用string,交到poj会编译错误

判重离散化都可以用set,挺方便的

#include<cstring>
#include<vector>
#include<cstdio>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e3 + 10;
int link[MAXN << 1], vis[MAXN << 1], n, m, cnt;
char s[MAXN << 1][20], str[MAXN << 1][20];
vector<int> g[MAXN << 1];

bool check(char a[], char b[])
{
	int num = 0;
	REP(i, 0, n)
		num += a[i] != b[i];
	return num == 1;
}

bool dfs(int u)
{
	REP(i, 0, g[u].size())
	{
		int v = g[u][i];
		if(vis[v]) continue;
		vis[v] = 1;
		if(!link[v] || dfs(link[v]))
		{
			link[v] = u;
			return true;
		}
	}
	return false;
}

void add(char a[])
{
	_for(i, 1, cnt)
		if(!strcmp(s[i], a))
			return;
	strcpy(s[++cnt], a);
}

int main()
{
	while(scanf("%d%d", &n, &m) && n)
	{
		memset(link, 0, sizeof(link));
		memset(s, 0, sizeof(s));
		cnt = 0;
		
		_for(i, 1, m)
		{
			scanf("%s", str[i]);
			REP(j, 0, n)
			{
				if(str[i][j] == '*')
				{
					str[i][j] = '0'; add(str[i]);
					str[i][j] = '1'; add(str[i]);
				    break;
				}
				if(j == n - 1) add(str[i]);
			}
		}
		m = cnt;
	
		_for(i, 1, m) g[i].clear();
		_for(i, 1, m)
			_for(j, i + 1, m)
				if(check(s[i], s[j]))
				{
					g[i].push_back(j);
					g[j].push_back(i);
				}
	
		int ans = 0;
		_for(i, 1, m)
		{
			memset(vis, 0, sizeof(vis));
			if(dfs(i)) ans++;
		}
		printf("%d\n", m - ans / 2);
	}

	return 0;
}

2.8(二分图)

昨天休息,去了书城

感觉恢复的差不多了

坚持早睡早起,晨跑

搞起

二分图进展有点慢,因为题目的建模我要想挺久

那就慢一点吧

poj 3020(最小路径覆盖)

这题做了n久

卡在怎么建图

我受前面题目的影响,一直在往最小点覆盖方向想

这样的话一个点就是一条边

而这道题不一样,它是一个点就是二分图中的节点,而连接方式是边

节点倍长一下,对称,最后答案除以2

最小路径覆盖指的的是用最少的路径来覆盖所有节点

最小路径覆盖 = 总节点 - 最大匹配

这里我想到了如果有些节点没有边怎么办

从公式来看,这些多余的点就多在总节点这个地方,最小路径覆盖数就会加

刚好是符合题意的

然后这里连边的时候注意越界的问题,越界就return保险一点

我因为没有return,连到了之前输入的图的点了

#include<cstdio>
#include<vector>
#include<cstring>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 50;
const int MAXM = 410;
int id[MAXN][MAXN], link[MAXM], vis[MAXM], n, m, cnt;
char s[MAXN][MAXN];
vector<int> g[MAXM];

void build(int x, int y, int xx, int yy)
{
	if(s[xx][yy] != '*') return;
	g[id[x][y]].push_back(id[xx][yy]);
}

bool dfs(int u)
{
	REP(i, 0, g[u].size())
	{
		int v = g[u][i];
		if(vis[v]) continue;
		vis[v] = 1;
		if(!link[v] || dfs(link[v]))
		{
			link[v] = u;
			return true;
		}
	}
	return false;
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		memset(link, 0, sizeof(link));
		memset(id, 0, sizeof(id));
		memset(s, 0, sizeof(s));
		cnt = 0;
		
		scanf("%d%d", &n, &m);
		_for(i, 1, n) scanf("%s", s[i] + 1);
		_for(i, 1, n)
			_for(j, 1, m)
				if(s[i][j] == '*')
				{
					id[i][j] = ++cnt;
					g[cnt].clear();
				}
					
		_for(i, 1, n)
			_for(j, 1, m)
				if(s[i][j] == '*')
				{
					build(i, j, i - 1, j);
					build(i, j, i + 1, j);
					build(i, j, i, j - 1);
					build(i, j, i, j + 1);
				}	
		
		int ans = 0;
		_for(i, 1, cnt)
		{
			memset(vis, 0, sizeof(vis));
			if(dfs(i)) ans++;
		}
		printf("%d\n", (cnt * 2 - ans) / 2);
	}
	
	return 0;
}

poj 1466(最大独立集)

选一些点,这些点之间两两没有边相连,为独立集

点最多为最大独立集

记住一个定理

最小点覆盖+最大独立集 = 总节点

除去最小点覆盖的点,剩下的点就是最大独立集

如果剩下点有边相连,那么最小点覆盖就没有覆盖完,矛盾

反证法挺有用的

点覆盖最小,独立集就最大

所以最大独立集=总节点-最大匹配

这道题是二分图对称的,所以答案除以2

这道题有个坑点就是从0开始

而匈牙利算法0是默认没有点相连的

所以输入的时候+1处理一下

以后遇到标号从0开始都可以处理一下成从1开始

#include<cstdio>
#include<vector>
#include<cstring>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 510;
int link[MAXN], vis[MAXN], n;
vector<int> g[MAXN];

bool dfs(int u)
{
	REP(i, 0, g[u].size())
	{
		int v = g[u][i];
		if(vis[v]) continue;
		vis[v] = 1;
		if(!link[v] || dfs(link[v]))
		{
			link[v] = u;
			return true;
		}
	}
	return false;
}

int main()
{
	while(~scanf("%d", &n))
	{
		memset(link, 0, sizeof(link));
		_for(i, 1, n) g[i].clear();
		
		_for(i, 1, n)
		{
			int x, m;
			scanf("%d: (%d)", &x, &m);
			x++;
			while(m--)
			{
				int y; scanf("%d", &y);
				g[x].push_back(++y);
			}
		}
		
		int ans = 0;
		_for(i, 1, n)
		{
			memset(vis, 0, sizeof(vis));
			if(dfs(i)) ans++;
		}
		printf("%d\n", (n * 2 - ans) / 2);	
	} 
	
	return 0;
}

poj 3692(最大独立集+建图)

这道题要在二分图中求n个点,任意属于两个集合中的点都有边相连

而独立集恰好是任意两个集合的点都没有边相连

所以我们建图可以反过来

把不存在的边连上,存在的边删除

然后最大独立集就是答案

至此二分图部分结束了,最大匹配,最小点覆盖,最小边覆盖,最大独立集都过了一遍

还是蛮有收获的

明天开启数学问题

加油

#include<cstdio>
#include<vector>
#include<cstring>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 210;
int link[MAXN], vis[MAXN], a[MAXN][MAXN], n, m, k;
vector<int> g[MAXN];

bool dfs(int u)
{
	REP(i, 0, g[u].size())
	{
		int v = g[u][i];
		if(vis[v]) continue;
		vis[v] = 1;
		if(!link[v] || dfs(link[v]))
		{
			link[v] = u;
			return true;
		}
	}
	return false;
}

int main()
{
	int kase = 0;
	while(scanf("%d%d%d", &n, &m, &k) && n)
	{
		memset(link, 0, sizeof(link));
		memset(a, 0, sizeof(a));
		_for(i, 1, n) g[i].clear();
		
		while(k--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			a[u][v] = 1;
		}
		_for(i, 1, n)
			_for(j, 1, m)
				if(!a[i][j])
					g[i].push_back(j);
		
		int ans = 0;
		_for(i, 1, n)
		{
			memset(vis, 0, sizeof(vis));
			if(dfs(i)) ans++;
		}
		printf("Case %d: %d\n", ++kase, n + m - ans);	
	} 
	
	return 0;
}

 

2.9(快速幂+质数)

因为22号回去,所以最后一个专题就是数学了

打算搞一波数学,然后留两天敲敲模板就好了

搜索,动态规划,图论,字符串这些我都有一定的知识储备了

但是在数学这部分比较薄弱

搞起

 

「一本通 6.1 练习 3」越狱(快速幂+细节)

先来一道快速幂热身

快速幂理解本质,代码就不会写错了,不要背模板

然后是一些数学题目要注意的细节

一个是MOD,可以先写好函数,这样就不会出错了

想乘要转long long

相减可能为负,要加MOD

然后这个MOD的话,a的b次方,a可以先MOD,b不能MOD,它是指数

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i < (b); i++) 
using namespace std;

const int MOD = 100003;
typedef long long ll;

int add(int a, int b) { return (a + b) % MOD; }
int mul(int a, int b) { return 1ll * a * b % MOD; }

int binpow(int a, ll b)
{
	int res = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = mul(res, a);
		a = mul(a, a);
	}
	return res;
}

int main()
{
	int m; ll n;
	scanf("%d%lld", &m, &n);
	m %= MOD;
	int ans = binpow(m, n) - mul(m, binpow(m - 1, n - 1));
	if(ans < 0) ans += MOD;
	printf("%d\n", ans);
	return 0;
}

欧拉筛

还是那样,学算法要深刻理解算法,这样一来一些拓展题做得出来,二来代码可以精确根据思想写出来

我尝试自己从头到尾讲一遍线性筛法

埃式筛法的话,核心思想是质数的倍数是合数,那么就用质数去筛掉。

问题在于一个数会被筛多次,比如12 = 2x6 = 3x4

12既被质数2筛了一次,又被质数3筛了一次

那么欧拉筛就是使得每个数中被其最小质因子筛一次,从而优化

那么这么做呢

首先我们枚举每一个数,是质数就标记,加入数组,这是常规操作,和埃式筛法一样

然后我们把当前的数当作倍数,去乘已经得到的质数,从而去筛

那么关键是保证这个质因子一定是最小的

核心操作是当前数是当前质数的倍数的时候,就break,停止

为什么呢

假设当前数为i,质数为p,那么i % p == 0,即i可以表示为np时

如果不break掉,就继续到下一个质数,假设是(p + k)

那么新的数就是np乘上(p+k),我们是默认(p+k)为np乘上(p+k)的最小质因子

然而不是,因为np乘上(p+k)等价于n(p+k)乘上p,显然存在p为更小的质因子

所以从这往后都存在更小的质因子p,所以就break了

所以一直到i % p == 0的时候就停止就好了

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 100 + 10;
bool vis[MAXN];
vector<int> p;

void get_prime()
{
	vis[0] = vis[1] = true;
	REP(i, 2, MAXN)
	{
		if(!vis[i]) p.push_back(i);
		REP(j, 0, p.size())
		{
			if(i * p[j] >= MAXN) break;
			vis[i * p[j]] = true;
			if(i % p[j] == 0) break;
		}
	}
}

int main()
{
	get_prime();
	REP(i, 0, p.size()) printf("%d ", p[i]);
	return 0;
}

质因数分解

用2到根号n的数试除法就ok了

当然可以优化,比如预处理质数,用质数去试除

更优化的可以用最小质因子去除

void work(int n)
{
	for(int i = 2; i * i <= n; i++)
	{
		if(n % i == 0)
		{
			p[++cnt] = i;
			while(n % i == 0)
			{
				n /= i;
				c[cnt]++;
			}
		}
	}
	if(n > 1) p[++cnt] = n, c[cnt]++;
}

「一本通 6.2 例 1」Prime Distance(筛法思想应用)

这题写了好久,因为有坑,我调半天没调调出来

这题应用了筛法的思想,即质数的倍数是合数,用质数去筛

那么我们就去筛l到r这个区间

那么首先需要初始化质数

那么初始化1到根号r就行了

因为比如n = a * b,a或b有一个是小于等于根号n的

我用的线性筛去筛了一下

然后枚举质数,去筛这个区间

标记筛过可以用unorered_map 也可以用数组,下标减去就好

数组时间空间上都更优秀,unorered_map写起来方便一些简洁一些

然后这里有个巨坑

就是有一组数据r为int的最大值

所以for的时候会溢出,变成负数,然后就死循环超时了

太坑了

还有0 1为合数,要特判一下

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e5;
unordered_map<int, bool> v;
vector<int> p, ve;
bool vis[MAXN];

void get_prime()
{
	vis[0] = vis[1] = true;
	REP(i, 2, MAXN)
	{
		if(!vis[i]) p.push_back(i);
		REP(j, 0, p.size())
		{
			if(i * p[j] >= MAXN) break;
			vis[i * p[j]] = true;
			if(i % p[j] == 0) break;
		}
	}
}

int main()
{
	get_prime();
	int l, r;
	while(~scanf("%d%d", &l, &r))
	{
		v.clear();
		REP(j, 0, p.size())
		{
			if(1ll * p[j] * p[j] > r) break;
			_for(i, max(l / p[j], 2), r / p[j])
				v[i * p[j]] = true;
		}
		
		ve.clear();
		_for(i, l, r)
		{
			if(i < 0) break;
			if(!v[i] && i != 1) ve.push_back(i);
		}
				
		if(ve.size() <= 1) puts("There are no adjacent primes.");
		else
		{
			int mi = 1e9, t1, t2, ma = 0, t3, t4;
			REP(i, 1, ve.size())
			{
				if(mi > ve[i] - ve[i-1])
				{
					mi = ve[i] - ve[i-1];
					t1 = ve[i-1], t2 = ve[i];
				}
				if(ma < ve[i] - ve[i-1])
				{
					ma = ve[i] - ve[i-1];
					t3 = ve[i-1], t4 = ve[i];
				}
			}
			printf("%d,%d are closest, %d,%d are most distant.\n", t1, t2, t3, t4);
		}
	}
	
	return 0;
}

「一本通 6.2 练习 4」Sherlock and His Girlfriend(素数筛)

水题,按照质数合数分两份就行了,是一个二分图

注意特判一下只有质数的情况

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e5 + 10;
bool vis[MAXN];
vector<int> p;

void get_prime()
{
	vis[0] = vis[1] = true;
	REP(i, 2, MAXN)
	{
		if(!vis[i]) p.push_back(i);
		REP(j, 0, p.size())
		{
			if(i * p[j] >= MAXN) break;
			vis[i * p[j]] = true;
			if(i % p[j] == 0) break;
		}
	}
}

int main()
{
	get_prime();
	int n; scanf("%d", &n);
	
	if(n <= 2)
	{
		if(n == 1) printf("1\n1\n");
		if(n == 2) printf("1\n1 1\n");
		return 0;
 	}
 	
	puts("2");
	_for(i, 1, n)
	{
		if(vis[i+1]) printf("1 ");
		else printf("2 ");
	}
	puts("");
	
	return 0;
}

「一本通 6.2 练习 2」轻拍牛头(筛法思想+小优化)

正常思路去枚举是n方

这里利用筛法的思想

把每个数去筛

这里会T掉一个点,这里把重复的合并在一起就可以ac了

我没想到这个优化这么明显。其实也只能这么优化了,没有什么其他办法了

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e6 + 10;
int a[MAXN], v[MAXN], b[MAXN], cnt[MAXN], n, ma;

int main()
{
	scanf("%d", &n);
	_for(i, 1, n) 
	{
		scanf("%d", &a[i]);
		ma = max(ma, a[i]);
		cnt[a[i]]++;
	}
	
	int num = 0;
	_for(i, 1, ma)
		if(cnt[i])
			b[++num] = i;
	
	_for(i, 1, num)
		for(int j = b[i]; j <= ma; j += b[i])
			v[j] += cnt[b[i]];
	_for(i, 1, n) printf("%d\n", v[a[i]] - 1);
	
	return 0;
}

2.10(质数+约数)

「一本通 6.2 练习 5」樱花(转化式子+求因子个数)

这道题我卡在了这么转化题目那个式子的那一步

然后我后来不小心看到了可以把y化成n!+ t

这一步挺秀的,不知道怎么想出来的

然后转化后就可以发现其实是求n!的平方的因子个数

上次有道题也是一个式子最后转化成求因子个数的。所以以后打表找规律的时候可以往这个方向想,看符不符合

求因子个数可以质因数分解,然后答案就是(cnt[1] + 1) (cnt[2]+1)……

这里有两种做法

一种是直接暴力枚举1到n,对于每一个数去做质因数分解

当然试除法效率很低

更优的方法是预处理出根号n的质数,直接用质数去试除

还有更优的,也就是上次我在cf做到一道题就这么写

就是在欧拉筛的时候存一下每个数的最小质因子,然后用最小质因子去试除,这样是非常非常快的

也就是要预处理1到n的质数

还有第二种做法,就是枚举每一个质数p,个数就是n / p + n / (p * p) + n / (p * p * p)……

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e6 + 10;
const int MOD = 1e9 + 7;
int m[MAXN], cnt[MAXN];
bool vis[MAXN];
vector<int> p;

void get_prime()
{
	vis[0] = vis[1] = true;
	REP(i, 2, MAXN)
	{
		if(!vis[i]) p.push_back(i), m[i] = i;
		REP(j, 0, p.size())
		{
			if(i * p[j] >= MAXN) break;
			vis[i * p[j]] = true;
			m[i * p[j]] = p[j];
			if(i % p[j] == 0) break;
		}
	}
}

void deal(int n)
{
	while(n != 1)
	{
		int t = m[n];
		while(n % t == 0)
		{
			n /= t;
			cnt[t] += 2;
		}
	}
}

int main()
{
	get_prime();
	int n; scanf("%d", &n);
	_for(i, 1, n) deal(i);
	
	int ans = 1;
	REP(i, 1, MAXN)
		if(cnt[i])
			ans = 1ll * ans * (cnt[i] + 1) % MOD;
	printf("%d\n", ans);
	
	return 0;
}

求1~N所有数的正约数集合

用试除法n根号n比较慢

这里用倍数法,复杂度为n + n / 2 + n / 3……=nlogn

vector<int> factor[MAXN];
REP(i, 1, MAXN)
	for(int j = i; j < MAXN; j += i)
		factor[j].push_back(i);

「一本通 6.3 例 1」反素数 Antiprime(观察+搜索)

首先要推这种数有什么特点

因为涉及到因子个数,所以马上联想到质因数分解

可以发现这种数的质因子一定是连续的质数

如果不是,那一定可以找到因子个数相同但是更小的数

所以这种数一定是由前10个质数组成,前10个质数相乘已经快爆int了

所以就可以搜索,枚举每一个质数的幂次

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

int p[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
int n, ans, t;

void dfs(int now, int i, int num)
{
	if(i > 9) return;
	if(num > t)
	{
		t = num;
		ans = now;
	}
	if(num == t) ans = min(ans, now);
	
	int cnt = 0; 
	while(1ll * now * p[i] <= n)
	{
		cnt++;
		now *= p[i];
		dfs(now, i + 1, num * (cnt + 1));
	}
}

int main()
{
	scanf("%d", &n);
	dfs(1, 0, 1);
	printf("%d\n", ans);
	return 0;
}

最大公约数gcd

了解一些定理

gcd(a, b) = gcd(a-b, b)

因为对于任意一个公约数d

d|a d|b 可得d|(a-b)

同时推出gcd(a%b, b)减很多次就是模

根据gcd(a, b) = gcd(a-b, b)和奇偶可以求大整数的GCD

 

「一本通 6.3 练习 1」X-factor Chain(质因数分解+排列组合)

质因数分解一下就好

序列个数相当于n个数的排列,有一些重复,把重复的除掉就好

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

typedef long long ll;
vector<int> ve;
int cnt[20], n, num;
ll f[30];

int main()
{
	f[0] = f[1] = 1;
	_for(i, 2, 20) f[i] = f[i-1] * i;
	
	while(~scanf("%d", &n))
	{
		ve.clear();
		memset(cnt, 0, sizeof(cnt));
		num = 0;
		
		for(int i = 2; i * i <= n; i++)
			if(n % i == 0)
			{
				ve.push_back(i); num++;
				while(n % i == 0)
				{
					n /= i;
					cnt[num]++;
				}
			}
		if(n > 1) ve.push_back(n), cnt[++num] = 1;
		
		int sum = 0;
		_for(i, 1, num) sum += cnt[i];
		ll ans = f[sum];
		_for(i, 1, num) ans /= f[cnt[i]];
		printf("%d %lld\n", sum, ans);
	}
	
	return 0;
}

2.13(约数+同余)

前两天过年就没训练了

今天回归训练

这两天应该很开心,但我却遭遇了挫折,挺伤心的

但正如我最近读的斯多葛哲学

控制能控制的,不能控制的努力释怀。事情已经发生,无法控制,无法挽回

但我能控制我自己的态度,是垂头丧气一蹶不振还是找回斗志重新奋斗

同时对已经发生的事情采用宿命论的态度,我就把它当做我的命运吧

一个优秀的人应该迎接命运给它的所有安排。无论发生什么,都是为了最后有个最好的结果

不管怎么样,提升自己,从编程,从英语,从外表,从身体健康,从读书,才是最有价值的事情

「JLOI2014」聪明的燕姿(数论+搜索)

这题挺难,我独立想没有想出来

后来发现是一道紫题,是一次省选里的题目,难怪呢

首先知道一个公式

S = (1 + p1 + p1 * p1……)(1 + p2 + p2 * p2……)……

其中p1,p2是x的质因子,每一个项一直加到最高次数

这个挺好理解的,这样相乘能乘出x的每一个因子

其实我想到这个了,但不知道接下来怎么做了

其实就是搜索,我一直往数学方向想,我可能太少做这种数论+搜索的题目了,没有往搜索这个方向想

搜索可以看作较为优秀的暴力,因为很多无用状态可以排除

我当时一直想把S质因数分解。其实是反过来,枚举因子看能不能除以S。

很多题目反过来想就对了,我要牢记这一点

以后当我做题卡壳的时候,就要告诉自己也许反过来想就有思路了

 

我们可以枚举每一个质因子p,然后枚举和,看和是不是S的因子

这里的S最大到10的9次方

所以有些p可能非常大,1+p为因子

那么我们这里就分类讨论

先看简单的一类

对于一些1+p+p*p……的,也就是 不是1+p的

我们可以预处理出根号S内的质因数,线性筛一些,枚举就完事

对于1+p的

我们要判断p是否为质数,而且没有与之前的重复,如果可以的话就加入答案

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e5;
bool vis[MAXN];
vector<int> p, ans;
int n;

void get_prime()
{
	vis[0] = vis[1] = true;
	REP(i, 2, MAXN)
	{
		if(!vis[i]) p.push_back(i);
		REP(j, 0, p.size())
		{
			if(i * p[j] >= MAXN) break;
			vis[i * p[j]] = true;
			if(i % p[j] == 0) break;
		}
	}
}

bool check(int num)
{
	if(num < MAXN) return !vis[num];
	REP(j, 0, p.size())
	{
		if(1ll * p[j] * p[j] > num) return true;
		if(num % p[j] == 0) return false;
	}
}

void dfs(int k, int now, int num)
{
	if(num == 1) { ans.push_back(now); return; }
	if((k == -1 || num - 1 > p[k]) && check(num - 1)) ans.push_back(now * (num - 1));
	
	REP(j, k + 1, p.size())
	{
		if(1ll * p[j] * p[j] > num) break;
		for(long long sum = 1 + p[j], t = p[j]; sum <= num; t *= p[j], sum += t)
			if(num % sum == 0) 
				dfs(j, now * t, num / sum);
	}
}

int main()
{
	get_prime();
	while(~scanf("%d", &n))
	{
		ans.clear();
		dfs(-1, 1, n);
		sort(ans.begin(), ans.end());
		printf("%d\n", ans.size());
		REP(i, 0, ans.size()) printf("%d ", ans[i]);
		if(ans.size()) puts("");
	}
	return 0;
}

扩展欧几里得解(解ax+by=c的不定方程)

void exgcd(int a, int b, int& d, int& x, int& y)
{
	if(!b) { d = a; x = 1; y = 0; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b) ; } //y -= x * (a / b) 要记忆 
}

用来解ax+by=c的不定方程

其实用手算一下就可以推出来的

上面代码是求ax+by = gcd(a, b)的一组解

有几个结论

对于ax + by = c

如果gcd(a, b) | c有解,否则无解(整数解)

有解时同乘个系数即可

通解为x = x0 + k * b/gcd

y = y0 - k * a/gcd

「一本通 6.4 例 1」青蛙的约会(扩展欧几里得+细节)

这里有几个细节要注意

(1)数论题最好开long long.我因为没开long long WA了一个点

(2)结论要记好。d | c时有解,得出x与y时要乘一个系数回去。通解公式要记得,注意复数取模

(3)ax+by=c,a和b都要为正,c可以为负。因为要求最大公因数,a和b都要是正数

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

typedef long long ll;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
	ll x0, y0, m, n, L;
	ll x, y, d;
	scanf("%lld%lld%lld%lld%lld", &x0, &y0, &m, &n, &L);
	if(m - n < 0) swap(m, n), swap(x0, y0);
	exgcd(m - n, L, d, x, y);
	if((y0 - x0) % d != 0) puts("Impossible");
	else
	{
		x *= (y0 - x0) / d, y *= (y0 - x0) / d;
		ll mod = L / d;
		x %= mod; 
		if(x < 0) x += mod;
		printf("%lld\n", x);
	}
	return 0;
}

线性同余方程

ax = b(mod m),解x为同余方程,x的指数为1,所以叫线性同余方程

转化成不定方程即可,可以化为ax + my = b

如果有解,恰有d个解

特殊地,如果b=1,即ax = 1(mod m) x为amodm的逆

这时可推出gcd(a, m) = 1即a与m互质才有解

「NOIP2012」同余方程(线性同余方程)

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

typedef long long ll;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
	ll a, b, x, y, d;
	scanf("%lld%lld", &a, &b);
	exgcd(a, b, d, x, y);
	ll mod = b / d;
	x %= mod; if(x < 0) x += mod; 
	printf("%lld\n", x);
	return 0;
}

2.14(同余)

乘法逆元

ax=1(mod p) x为逆元

逆元存在前提是a与p互质

p为质数时可以用费马小定理,a^p-2即为逆元,此时互质等价于a不是p的倍数

p不为质数时解同余方程,用扩展欧几里德

「一本通 6.4 例 3」Sumdiv(等比数列求和+逆元)

用约数和公式,加等比数列求和

涉及到除法,也就是要用逆元

9901是质数,所以只要p-1不是9901的倍数就可以用费马小定理

这里特判一下,如果是p-1是9901的倍数,那么p mod 9901 = 1    1+p+p*p……p^n = n + 1

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MOD = 9901;

vector<int> ve;
int cnt[30], m;

ll add(ll a, ll b) { return (a + b) % MOD; }
ll mul(ll a, ll b) { return a * b % MOD; }

ll binpow(ll a, ll b)
{
	ll res = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = mul(res, a);
		a = mul(a, a);
	}
	return res;
}

ll inv(ll a) { return binpow(a, MOD - 2); }

int main()
{
	ll a, b;
	scanf("%lld%lld", &a, &b);
	for(int i = 2; i * i <= a; i++)
		if(a % i == 0)
		{
			ve.push_back(i);
			while(a % i == 0)
			{
				a /= i;
				cnt[m]++;
			}
			m++;
		}
	if(a > 1) ve.push_back(a), cnt[m++] = 1;
	
	ll ans = 1;
	REP(i, 0, ve.size())
	{
		ll p = ve[i], n = cnt[i] * b;
		if((p - 1) % MOD == 0)
		{
			ans = mul(ans, n + 1);
			continue;
		}
		ll t = binpow(p, n + 1);
		t--; if(t < 0) t += MOD;
		ans = mul(ans, mul(t, inv(p - 1)));
	}
	printf("%lld\n", ans);
	
	return 0;
}

中国剩余定理

其实蛮简单的,可以用来解同余方程组

x = a1(mod m1)

x = a2(mod m2)

.

.

x = an(mod mn)

其中m1, m2……mn两两互质

 

设M = m1 * m2……*mn

Mi = M / mi

设ti为Mi模mi的逆元(mi不一定为质数,所以要用扩展欧几里德)

那么答案x = a1M1t1 + a2M2t2……

很容易正面,把x代入上面任意一个方程都成立

这个思路有点像拉格朗日插值法

这个解像是凑出来的

这个解在modM意义下有唯一解

「一本通 6.4 例 4」曹冲养猪(中国剩余定理模板)

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MAXN = 1e3 + 10;
ll a[MAXN], m[MAXN], M = 1;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
	int n; scanf("%d", &n);
	_for(i, 1, n) 
	{
		scanf("%lld%lld", &m[i], &a[i]);
		M *= m[i];
	}
	
	ll ans = 0;
	_for(i, 1, n)
	{
		ll Mi = M / m[i];
		ll x, y, d;
		exgcd(Mi, m[i], d, x, y);
		ans += a[i] * Mi * x;
	}
	printf("%lld\n", (ans % M + M) % M);
	
	return 0;
}

扩展欧几里德解同余方程组

理解了就简单了

就是利用前面的解

假设前k-1个同余方程的解为x

mi的lcm为M

那么通解就为x+kM

那么把这个代到第k个方程组

x+kM=ak(mod mk)

exgcd解方程组就OK了

如果无解那么这个同余方程组就无解

「一本通 6.4 例 5」Strange Way to Express Integers(扩展欧几里德解同余方程组模板)

有个细节,不然wa一个点

就是过程中要始终控制数据的大小,不然会爆long long

两个地方

一个是每次x算完要mod M

另一个是欧几里德算出解后要取模控制一下大小,不要忘了这一步

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MAXN = 1e5 + 10;
ll a[MAXN], m[MAXN];

ll gcd(ll a, ll b) { return !b ? a : gcd(b, a % b); }
ll lcm(ll a, ll b) { return a / gcd(a, b) * b; }

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
	int n; 
	while(~scanf("%d", &n))
	{
		_for(i, 1, n) scanf("%lld%lld", &m[i], &a[i]);
		ll x = a[1], M = m[1], flag = 1;
		_for(i, 2, n)
		{
			ll x0, y0, d;
			exgcd(M, m[i], d, x0, y0);
			if((a[i] - x) % d != 0)
			{
				flag = 0;
				break;
			}
			x0 *= (a[i] - x) / d % (m[i] / d);
			x += x0 * M;
			M = lcm(M, m[i]);
			x %= M;
		}
		if(!flag) puts("-1");
		else printf("%lld\n", (x % M + M) % M);
	}
	
	return 0;
}

2.15(同余)

「一本通 6.4 练习 1」荒岛野人(枚举答案验证+exgcd)

这道题想挺久没什么思路,看了题解

我卡在Li这个点不知道这么处理

我开始以为要枚举时间,然后每一次只留下活着的去判断

但发现不知道这么判断

因为这个不相等实在是太不好搞了,完全没思路想着这么弄出个M出来

没想到题解是直接枚举答案M

这是一个很经典的思路

不知道怎么得出答案,那就枚举答案来验证。尤其是在这种求最大最小的题目中

其实我一开始有想到二分答案,但发现它不满足二分答案的性质

没想到已经很靠近了,就是枚举答案,最终复杂度也不高

枚举M,然后枚举两个人是否有可能相等,列一个方程

方程无解或者相等的天数有人死了就是ok的

另外用exgcd时有两个我要注意的地方

一个是类型是void,我老是写成ll

一个是a和b一定要都为正数,exgcd前要判断一下

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

typedef long long ll;
ll c[20], p[20], l[20], m;
int n;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

bool check(int i, int j)
{
	ll d, x, y;
	if(p[i] - p[j] < 0) swap(i, j);
	exgcd(p[i] - p[j], m, d, x, y);
	if((c[j] - c[i]) % d != 0) return true;
	x *= (c[j] - c[i]) / d;
	ll mod = m / d;
	return (x % mod + mod) % mod > min(l[i], l[j]);
}

bool work()
{
	_for(i, 1, n)
		_for(j, i + 1, n)
			if(!check(i, j))
				return false;
	return true;
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n) 
	{
		scanf("%lld%lld%lld", &c[i], &p[i], &l[i]);
		m = max(m, c[i]); 
	} 
	while(!work()) m++;
	printf("%lld\n", m);
	return 0;
}

还有一个高次同余方程的知识点,用BGBS算法,不太想弄,以后遇到了再说吧

今天没什么兴致训练了

明天开启组合数学吧

 

2.16(组合数学)

首先复习了n方组合数递推,用来预处理组合数

n方预处理组合数

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e3 + 10;
int c[MAXN][MAXN]; //c[i][j]表示从i个数中选j个 

void init()
{
	REP(i, 0, MAXN) c[i][0] = 1; //初始化,要不递推个啥,全是0。这个初始化包含所有0的情况,下面递推ij就不用到0了 
	REP(i, 1, MAXN)
		_for(j, 1, i)
			c[i][j] = c[i-1][j] + c[i-1][j-1]; //把一个数提出来,i-1,这个数选不选就是j和j-1 
}

int main()
{
	init();
	_for(i, 1, 10)
		_for(j, 1, i)
			printf("i: %d j: %d c: %d\n", i, j, c[i][j]); 
	
	return 0;
}

「NOIP2011」计算系数(二项式定理)

高中数学知识

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++) 
#define _for(i, a, b) for(int i = (a); i <= (b); i++) 
using namespace std;

const int MAXN = 1e3 + 10;
const int MOD = 10007;
int c[MAXN][MAXN]; 

int add(int a, int b) { return (a + b) % MOD; }
int mul(int a, int b) { return 1ll * a * b % MOD; }

void init()
{
	REP(i, 0, MAXN) c[i][0] = 1;
	REP(i, 1, MAXN)
		_for(j, 1, i)
			c[i][j] = add(c[i-1][j], c[i-1][j-1]);
}

int cal(int a, int b)
{
	int res = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = mul(res, a);
		a = mul(a, a);
	}
	return res;
}

int main()
{
	init();
	int a, b, k, n, m;
	scanf("%d%d%d%d%d", &a, &b, &k, &n, &m);
	printf("%d\n", mul(c[k][m], mul(cal(a, n), cal(b, m))));
	return 0;
}

不知道为什么最近有点累了……

我21号回学校,发现我的寒假只有五六天了

寒假大部分时间都在编程训练,最后几天给自己缓缓吧

想干嘛干嘛吧,编程的话看心情

 

2.21

今天敲一敲模板吧,今晚回校明天就开始集训了

我发现这几天少了编程,生活真的空虚了很多

就是很无聊,也不知道干啥。

像出去玩,看书看电影啥的,可以当做生活的调剂,但你要我一整天都干这个那就太无聊了

还是编程好

生活大部分是编程,小部分时间可以娱乐调剂一下

其实ACM本来就是一件挺快乐的事情

到时候开学时候也是用它来填补我的课余生活

如果没有ACM的话我的课余生活挺空虚的

能长期坚持一件事情,一定靠的是热爱

ACM,读书都是这样

真的喜欢它

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值