文章目录
- 修改数组
- 倍数问题
- 斐波那契
- 距离
- 剪格子
- 组合数问题
- 模拟散列表
修改数组
题目大意
给定一个长度为 的数组 ,数组中有可能有重复出现的整数。
现在小明要按以下方法将其修改为没有重复整数的数组。
小明会依次修改 。
当修改 时,小明会检查 是否在 中出现过。
如果出现过,则小明会给 加上 1;如果新的 仍在之前出现过,小明会持续给 加 1,直到 没有在 中出现过。
当 也经过上述修改之后,显然 数组中就没有重复的整数了。
现在给定初始的 数组,请你计算出最终的 数组。
输入格式
第一行包含一个整数 。
第二行包含 个整数 。
输出格式
输出 个整数,依次是最终的 。
数据范围:
输入样例
5
2 1 1 3 4
输出样例
2 1 3 4 5
表示 的代表元素
单链表并查集:表示链表中的下一个结点。 所在树的根节点:从开始往右找第一个没有被用过的位置。
#include <cstdio>
using namespace std;
const int maxn = 1e6 + 7;
int fa[maxn],a[maxn];
void init() //并查集初始化
{
for(int i = 1; i < maxn; i++) fa[i] = i;
}
int find(int x) //路径压缩
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
init();
int n;
scanf("%d",&n);
for(int i = 1; i <= n; i++)
{
int x;
scanf("%d",&x);
x = find(x);
printf("%d ",x);
fa[x] = x + 1;
}
return 0;
}
倍数问题
题目大意
众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。
现在小葱给了你 个数,希望你从这 个数中找到三个数,使得这三个数的和是 的倍数,且这个和最大。
数据保证一定有解。
输入格式
第一行包括 2 个正整数 。
第二行 个正整数,代表给定的 个数。
输出格式
输出一行一个整数代表所求的和。
数据范围:
给定的 个数均不超过
输入样例
4 3
1 2 3 4
输出样例
9
+贪心
: 表示在 个数中选 个数 %K 余数是
属性:值表示最大值
不选 : 选:
;
优化:
第 层只用了 层 且 严格小于 ,并且后面会用到第 层的数据
然后可以把三维 优化成一个二维
将余数相同是值存到同一个数组,然后排序取最值
不断优化取 0 ~
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define pb push_back
using namespace std;
const int maxn = 1e3 + 7;
vector<int>rest[maxn]; //存相同余数
int f[4][maxn];
int main()
{
int n, K, num;
scanf("%d%d",&n,&K);
for(int i = 0; i < n; i++)
{
scanf("%d",&num);
rest[num%K].pb(num); //余数相同的数存到一起
}
memset(f, -0x3f, sizeof f);
f[0][0] = 0;
for(int i = 0; i < K; i++)
{
sort(rest[i].begin(),rest[i].end());
reverse(rest[i].begin(),rest[i].end());
for(int j = 0; j < 3 && j < rest[i].size(); j++) //余数相同取前三大的数
{
int x = rest[i][j];
for(int k = 3; k; k--)
{
for(int v = 0; v < K; v++)
f[k][v] = max(f[k][v],f[k-1][((v-x)%K+K)%K]+x);
}
}
}
printf("%d\n",f[3][0]);
return 0;
}
斐波那契
题目大意
斐波那契数列大家都非常熟悉。它的定义是:
对于给定的整数 和 ,我们希望求出:
的值。
但这个值可能非常大,所以我们把它对 取模。
但这个数字依然很大,所以需要再对 求模。
输入格式
输入包含多组数据。
每组数据占一行,包含三个整数 。
输出格式
每组数据输出一个整数,表示答案。
每个数占一行。
数据范围:测试数据不超过100组
输入样例
2 3 5
输出样例
0
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
ll p;
ll qmul(ll a, ll b)
{
ll res = 0;
while (b)
{
if (b & 1) res = (res + a) % p;
a = (a + a) % p;
b >>= 1;
}
return res;
}
void mul(ll c[][2], ll a[][2], ll b[][2]) // c = a * b
{
static ll t[2][2];
memset(t, 0, sizeof t);
for (int i = 0; i < 2; i ++ )
for (int j = 0; j < 2; j ++ )
for (int k = 0; k < 2; k ++ )
t[i][j] = (t[i][j] + qmul(a[i][k], b[k][j])) % p;
memcpy(c, t, sizeof t);
}
ll F(ll n)
{
if (!n) return 0;
ll f[2][2] = {1, 1};
ll a[2][2] = {
{0, 1},
{1, 1},
};
for(ll k = n - 1; k; k >>= 1)
{
if (k & 1) mul(f, f, a); // f = f * a
mul(a, a, a); // a = a * a
}
return f[0][0];
}
ll H(ll m, ll k) // (F(m - 1) * F(k) - 1) mod F(m)
{
if (k % 2) return F(m - k) - 1;
else
{
if (k == 0 || m == 2 && m - k == 1) return F(m) - 1;
else return F(m) - F(m - k) - 1;
}
}
ll G(ll n, ll m) // (F(n) - 1) mod F(m)
{
if (m % 2 == 0) // m是偶数
{
if (n / m % 2 == 0) // n / m 是偶数
{
//cout << n << ' ' << m << ' ' << F(n % m) << endl;
if (n % m == 0) return F(m) - 1;
else return F(n % m) - 1;
}
else // n / m 是奇数
{
return H(m, n % m);
}
}
else // m 是奇数
{
if (n / m % 2 == 0 && n / 2 / m % 2 == 0) // n / m 是偶数,n / (2m) 是偶数
{
if (n % m == 0) return F(m) - 1;
else return F(n % m) - 1;
}
else if (n / m % 2 == 0 && n / 2 / m % 2) // n / m 是偶数,n / (2m) 是奇数
{
if (m == 2 && n % m == 1) return F(m) - 1;
else return F(m) - F(n % m) - 1;
}
else if (n / m % 2 && n / 2 / m % 2 == 0) // n / m 是奇数,n / (2m) 是偶数
{
return H(m, n % m);
}
else // n / m 是奇数,n / (2m) 是奇数
{
if (n % m % 2)
{
if (m == 2 && m - n % m == 1) return F(m) - 1;
else return F(m) - F(m - n % m) - 1;
}
else
{
return F(m - n % m) - 1;
}
}
}
}
int main()
{
ll n, m;
while(~scanf(" %lld%lld%lld",&n,&m,&p))
{
ll ans = (G(n + 2, m) % p + p) % p;
printf("%lld\n",ans);
}
return 0;
}
距离
题目大意
给出 个点的一棵树,多次询问两点之间的最短距离。
注意:
边是无向的。
所有节点的编号是 。
输入格式
第一行为两个整数 和 。 表示点数, 表示询问次数;
下来 行,每行三个整数 ,表示点 和点 之间存在一条边长度为 ;
再接下来 行,每行两个整数 ,表示询问点 到点 的最短距离。
树中结点编号从 1 到 。
输出格式
共 行,对于每次询问,输出一行询问结果。
数据范围:
输入样例
2 2
1 2 100
1 2
2 1
输出样例
100
100
Tarjan–离线求LCA
在线做法:读一个询问,处理一个,输出一个
离线做法:读完全部询问,再全部处理完,再全部输出
#include <cstdio>
#include <vector>
#include <cstring>
#define PII pair<int,int>
#define pb push_back
using namespace std;
typedef long long ll;
const int maxn = 1e4 + 7;
struct node{
int to, ne;
ll val;
};
node edge[maxn<<1];
int head[maxn<<1], tot = 0;
int fa[maxn];
int res[maxn<<2];
vector<PII> query[maxn<<1]; //first:存查询另外一个点 second:存查询编号
int dis[maxn<<1]; //存储每个点到根节点的距离
int st[maxn]; //0:未访问 1:正在访问 2:访问并结束回溯
void add(int x, int y, int w)
{
edge[++tot].to = y;
edge[tot].val = w;
edge[tot].ne = head[x];
head[x] = tot;
}
void dfs(int now, int ago)
{
for(int i = head[now]; i; i = edge[i].ne)
{
int to = edge[i].to;
if(to == ago) continue; //防止回搜
dis[to] = dis[now] + edge[i].val;
dfs(to, now);
}
}
int find(int x)
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
void tarjan(int now)
{
st[now] = 1;
for(int i = head[now]; i; i = edge[i].ne)
{
int to = edge[i].to;
if(!st[to])
{
tarjan(to);
fa[to] = now; //存父节点
}
}
for(auto it : query[now])
{
int to = it.first, id = it.second;
if(st[to] == 2) //计算
{
int root = find(to);
res[id] = dis[now] + dis[to] - (dis[root]<<1);
}
}
st[now] = 2;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1; i < n; i++)
{
int x, y; ll w;
scanf("%d%d%lld",&x,&y,&w);
add(x, y, w); add(y, x, w);
}
for(int i = 1; i <= m; i++) //查询m次,每次查询的编号
{
int x, y;
scanf("%d%d",&x,&y);
if(x != y)
{
query[x].pb({y, i});
query[y].pb({x, i});
}
}
for(int i = 0; i <= n; i++) fa[i] = i;
dfs(1,-1);
tarjan(1);
for(int i = 1; i <= m; i++) printf("%d\n",res[i]);
return 0;
}
剪格子
题目大意
如下图所示, 3×3 的格子中填写了一些整数。
我们沿着图中的红色线剪开,得到两个部分,每个部分的数字和都是 60。
本题的要求就是请你编程判定:对给定的 的格子中的整数,是否可以分割为两个连通的部分,使得这两个区域的数字和相等。
如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目。
如果无法分割,则输出 0。
输入格式
第一行包含两个整数 ,表示表格的宽度和高度。
接下来是 行,每行 个正整数,用空格分开。
输出格式
在所有解中,包含左上角的分割区可能包含的最小的格子数目。
如果无法分割,则输出 0。
数据范围:格子内的数均在1到10000之间。
输入样例
3 3
10 1 52
20 30 1
1 2 3
输出样例
3
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_set>
#define rep(i,a,b) for(auto i = (a); i < (b); i++)
#define per(i,a,b) for(auto i = (a); i > (b); i--)
#define pb push_back
using namespace std;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
const int maxn = 15, base = 131, inf = 0x3f3f3f3f;
int n, m;
int a[maxn][maxn];
bool st[maxn][maxn];
int sum, res = inf;
PII cands[maxn*maxn];
int fa[maxn*maxn];
unordered_set<ULL> hash_table;
int dir[4][2] = {-1, 0, 0, 1, 1, 0, 0, -1}; //方向
int find(int x) //路径压缩
{
if(fa[x] != x) fa[x] = find(fa[x]);
return fa[x];
}
bool inbound(int x, int l, int r) //判断位置是否合法
{
if(x < l || x >= r) return false; //不合法
return true;
}
bool check_connect(int k) //检查剩余的是否连通
{
rep(i, 0, n*m) fa[i] = i;
int cnt = n*m - k;
rep(i, 0, n)
rep(j, 0 , m)
if(!st[i][j])
{
rep(k, 0, 4)
{
int tx = i + dir[k][0], ty = j + dir[k][1];
if(!inbound(tx, 0, n) || !inbound(ty, 0, m)) continue;
if(st[tx][ty]) continue;
int p1 = find(i*m + j), p2 = find(tx*m + ty);
if(p1 != p2)
{
fa[p1] = p2;
cnt--;
}
}
}
if(cnt != 1) return false;
return true;
}
bool check_exists(int k)
{
static PII bk[maxn*maxn];
rep(i,0,k) bk[i] = cands[i];
sort(bk, bk + k);
ULL x = 0;
rep(i,0,k)
{
x = x*base + bk[i].first + 1;
x = x*base + bk[i].second + 1;
}
if(hash_table.count(x)) return true;
hash_table.insert(x);
return false;
}
void dfs(int s, int k)
{
if(s == sum/2)
{
if(check_connect(k)) res = min(res,k);
return;
}
vector<PII> points;
rep(i, 0, k)
{
int x = cands[i].first, y = cands[i].second;
rep(j, 0 , 4)
{
int tx = x + dir[j][0], ty = y + dir[j][1];
if(!inbound(tx,0,n) || !inbound(ty,0,m)) continue;
if(st[tx][ty]) continue;
cands[k] = {tx, ty};
if(k+ 1 < res && !check_exists(k+1)) points.pb({tx,ty});
}
}
sort(points.begin(), points.end());
reverse(points.begin(), points.end());
rep(i, 0, points.size())
{
if(!i || points[i] != points[i-1])
{
cands[k] = points[i];
int x = points[i].first, y = points[i].second;
st[x][y] = true;
dfs(s + a[x][y], k + 1);
st[x][y] = false;
}
}
}
int main()
{
scanf("%d%d",&m,&n);
rep(i, 0, n)
rep(j, 0, m)
scanf("%d",&a[i][j]),sum += a[i][j];
if(sum%2 == 0)
{
st[0][0] = true;
cands[0] = {0, 0};
dfs(a[0][0], 1);
}
if(res == inf) res = 0;
printf("%d\n",res);
return 0;
}
组合数问题
题目大意
组合数 表示的是从 个物品中选出 个物品的方案数。
举个例子,从 (1, 2, 3) 三个物品中选择两个物品可以有 (1, 2), (1, 3), (2, 3) 这三种选择方法。
根据组合数的定义,我们可以给出计算组合数 的一般公式:
=
其中 。
小葱想知道如果给定 , 和 ,对于所有的 , 有多少对 满足 是 的倍数。
输入格式
第一行有两个整数 ,其中 代表该测试点总共有多少组测试数据, 的意义见问题描述。
接下来 行每行两个整数 ,其中 的意义见问题描述。
输出格式
共 行,每行一个整数代表所有的 , 有多少对 满足 是 的倍数。
数据范围:
输入样例
1 2
3 3
输出样例
1
打表求组合数,过百分之90的数据
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e3 + 7;
ll f[maxn][maxn];
int mod;
void init()
{
for(int i = 0; i < maxn; i++)
for(int j = 0; j <= i; j++)
if(j == 0) f[i][j] = 1;
else f[i][j] = (f[i-1][j] + f[i-1][j-1])%mod;
}
int main()
{
int n, m, t;
scanf("%d%d",&t,&mod);
init();
while(t--)
{
ll res = 0;
scanf(" %d%d",&n,&m);
for(int i = 0; i <= n; i++)
{
int t = min(i,m);
for(int j = 0; j <= t; j++)
if(f[i][j]%mod == 0) res++;
}
printf("%d\n",res);
}
return 0;
}
正解打表求组合数+前缀和
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e3 + 7;
ll f[maxn][maxn], mod;
int s[maxn][maxn];
void init()
{
for(int i = 0; i < maxn; i++)
for(int j = 0; j <= i; j++)
{
if(j == 0) f[i][j] = 1;
else f[i][j] = (f[i-1][j] + f[i-1][j-1])%mod;
if(!f[i][j]) s[i][j] = 1;
}
for(int i = 0; i < maxn; i++)
{
for(int j = 0; j < maxn; j++)
{
if(i) s[i][j] += s[i-1][j];
if(j) s[i][j] += s[i][j-1];
if(i && j) s[i][j] -= s[i-1][j-1];
}
}
}
int main()
{
int n, m, t;
scanf("%d%d",&t,&mod);
init();
while(t--)
{
scanf("%d%d",&n,&m);
printf("%d\n",s[n][m]);
}
return 0;
}
模拟散列表
题目大意
维护一个集合,支持如下几种操作:
- ,插入一个数;
- ,询问数 是否在集合中出现过;
现在要进行
输入格式
第一行包含整数 ,表示操作数量。
接下来 行,每行包含一个操作指令,操作指令为 ,中的一种。
输出格式
对于每个询问指令 ,输出一个询问结果,如果 在集合中出现过,则输出 ,否则输出 。
每个结果占一行。
数据范围:
输入样例
5
I 1
I 2
I 3
Q 2
Q 5
输出样例
Yes
No
复杂的数据映射到小范围的数
时间复杂度O(n)
1.插入操作
(1)先找到槽
(2)赋值
(3)ne指针指向后面
(4)插入点上一个位置的数的指针指向当前位置
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1e5 + 7;
struct Node{
int val; //权值
int ne; //指针
};
int tot;
int head[maxn];
Node node[maxn];
int Insert(int x) //插入
{
int k = (x%maxn + maxn)%maxn;
node[++tot].val = x; //存点
node[tot].ne = head[k]; //ne指针指向后面
head[k] = tot; //指向当前位置
}
bool find(int x) //查询
{
int k = (x%maxn + maxn)%maxn;
for(int i = head[k]; i; i = node[i].ne)
if(node[i].val == x) return true;
return false;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
int num;
char op;
scanf(" %c%d",&op,&num);
if(op == 'I') Insert(num);
else
{
if(find(num)) printf("Yes\n");
else printf("No\n");
}
}
return 0;
}