蓝桥杯 第十讲 疑难杂题
1242. 修改数组
做法一:并查集变种
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6;
int p[N];
int n,x;
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
int main()
{
scanf("%d", &n);
for (int i = 1; i < N; i ++ )
{
p[i] = i;
}
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &x);
x = find(x); //找到大于x的第一个没有被用过的数
cout<<x<<' ';
p[x] = x+1; //将x的祖先指向x的下一个数
}
return 0;
}
1234. 倍数问题
朴素01背包做法(超时超空间)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10,K = 1e3 + 5;
int f[N][4][K];//f[i][j][k]表示前i个物品中,选取j个,总和%m的三数之和的最大值
int a[N];
int n,m;
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &a[i]);
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<4;j++)
{
for(int k=0;k<m;k++)
{
f[i][j][k] = -2e9;
}
}
}
for (int i = 0; i <= n; i ++ )
{
f[i][0][0] = 0;
}
for(int i=1;i<=n;i++)
{
for(int j = 1;j<4;j++)
{
for(int k=0;k<m;k++)
{
f[i][j][k] = max(f[i-1][j][k],f[i-1][j-1][((k - a[i])%m + m)%m] + a[i]);
}
}
}
cout<<f[n][3][0]<<endl;
return 0;
}
贪心优化
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5 + 10,K = 1e3 + 5;
int f[4][N]; //f[i][j]:选取了i个数,总和%m为j的总和的最大值
int n,m,x;
vector<int>a[N]; //a[i][j]:总和%m为i的数为j
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++ )
{
scanf("%d", &x);
a[x%m].push_back(x);
}
memset(f,-0x3f3f3f3f,sizeof f); //初始化负无穷
f[0][0] = 0;//选0个数,和为0的最大值为0
for(int i=0;i<m;i++) //枚举数%m为i
{
sort(a[i].begin(),a[i].end()); //贪心策略:排序并反转,选取较大的数
reverse(a[i].begin(),a[i].end());
for(int u=0;u<3 && u<a[i].size();u++) //枚举每个较大的数
{
int x = a[i][u];
for(int j=3;j>=1;j--) //由于要用到后面的状态,因此从后向前枚举选取的个数
{
for(int k=0;k<m;k++) //枚举选取的总和%m为k
{
f[j][k] = max(f[j][k],f[j-1][(k-x%m+m)%m]+x);
}
}
}
}
cout<<f[3][0]<<endl;
return 0;
}
1213. 斐波那契
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
LL p;
//龟速乘(解决相乘爆longlong的问题)
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;
}
//计算2x2矩阵相乘,c=a*b
void mul(LL c[][2], LL a[][2], LL b[][2]) {
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);
}
//快速幂(解决求斐波那契数列第n项的问题)
LL F(LL n) {
if (!n) return 0;
LL f[2][2] = {1, 1}; //f(1)=f(2)=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];
}
//(F(m-1) * F(n mod m) - 1) mod F(m)
LL H(LL m, LL k) {
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;
}
}
//求(F(n) - 1)mod F(m)
LL G(LL n, LL m) {
//m是偶数
if (m % 2 == 0) {
//n/2m也是偶数
if (n / m % 2 == 0) {
if (n % m == 0) return F(m) - 1;
else return F(n % m) - 1;
}
//n/2m是奇数
else {
return H(m, n % m);
}
}
//m是奇数
else {
//n/m是偶数,且n/2m是偶数
if (n / m % 2 == 0 && n / 2 / m % 2 == 0) {
if (n % m == 0) return F(m) - 1;
else return F(n % m) - 1;
}
//n/m是偶数,且n/2m是奇数
else if (n / m % 2 == 0 && n / 2 / m % 2) {
if (m == 2 && n % m == 1) return F(m) - 1;
else return F(m) - F(n % m) - 1;
}
//n/m是奇数,且n/2m是偶数
else if (n / m % 2 && n / 2 / m % 2 == 0) {
return H(m, n % m);
}
//n/m是奇数,且n/2m是奇数
else {
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;
cin >> n >> m >> p ;
cout << (G(n + 2, m) % p + p) % p << endl;
return 0;
}
1206. 剪格子
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 11,INF = 0x3f3f3f3f;
int g[N][N];
int S;
int n,m;
int ans = INF;
int dx[4] = {-1,1,0,0},dy[4] = {0,0,-1,1};
bool st[N][N];
void dfs(int x,int y,int sum,int cnt)
{
if(sum == S/2)
{
ans = min(ans,cnt);
return;
}
if(sum > S/2) return;
for (int i = 0; i < 4; i ++ )
{
int nx = x + dx[i],ny = y + dy[i];
if(nx<1||nx>m||ny<1||ny>n||st[nx][ny]) continue;
st[nx][ny] = true;
dfs(nx,ny,sum+g[nx][ny],cnt+1);
st[nx][ny] = false;
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
scanf("%d", &g[i][j]);
S += g[i][j];
}
}
if(S%2 == 1)
{
cout<<0<<endl;
return 0;
}
st[1][1] = true;
dfs(1,1,g[1][1],1);
if(ans == INF) ans = 0;
cout<<ans<<endl;
return 0;
}
523. 组合数问题
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 2005;
int n,m,k,t;
int c[N][N];//组合数
int s[N][N];//二维前缀和
int main()
{
scanf("%d%d", &t,&k);
for (int i = 0; i < N; i ++ ) //求组合数
{
for (int j = 0; j <= i; j ++ ) //C[i][j]:i在下,j在上,j一定不大于i才有意义
{
if(j == 0) c[i][j] = 1 % k;
else c[i][j] = (c[i-1][j-1]+c[i-1][j])%k; //c[i][j] = c[i-1][j] + c[i-1][j-1]
}
}
for (int i = 0; i < N; i ++ ) //求二维前缀和
{
for (int j = 0; j < N; j ++ )
{
if(j<=i && c[i][j] == 0) s[i][j] = 1;//整除余数为0,则c[i][j]为0,表示c[i][j]为k的倍数
if(i-1>=0) s[i][j] += s[i-1][j];
if(j-1>=0) s[i][j] += s[i][j-1];
if(i-1>=0 && j-1>=0) s[i][j] -= s[i-1][j-1];
}
}
while(t--)
{
scanf("%d%d", &n, &m);
cout<<s[n][m]<<endl;
}
return 0;
}
1171. 距离
tarjan算法:求二叉树结点的公共祖先
Tarjan – 离线求LCA
在线做法:读一个询问,处理一个,输出一个
离线做法:读完全部询问,再全部处理完,再全部输出
在深度优先遍历时,将所有点分成三大类
2号点:代表已经访问并结束回溯
1号点:代表正在访问
0号点:代表还没有访问过
其中所有2号点和正在搜索的1号点路径中已经通过并查集合并成一个集合
1、先求出所有点到根结点的距离depth[],设x号点和y号点的最近公共祖先是p,则x和y的最近距离等于depth[x] + depth[y] - 2 * depth[p]
2、在深度优先遍历1号点中的u点的时候,需要把u的查询的另外一个点的最短距离进行计算并存储,最后把u点合并到上一结点的集合
时间复杂度 O(n+m)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e4 + 10,M = 2*N;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];
int n,m,x,y,k;
vector<PII> query[N]; //first查询的另外一个点,second存查询编号
int res[M*2]; //res[i]:求第i个查询的结果
int p[N]; //并查集
int st[N];//结点状态:0为未访问,1为访问1次,2为已访问且已回溯
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
void dfs(int u,int dad) //求出根结点到其他结点的路径长度
{
for(int i=h[u];i!=-1;i=ne[i])
{
int j = e[i];
if(j == dad) continue;
dist[j] = dist[u] + w[i];
dfs(j,u);
}
}
void tarjan(int u) //求公共祖先的同时算出两点的最短路径
{
st[u] = 1;
for(int i=h[u];i!=-1;i=ne[i])
{
int j = e[i];
if(!st[j])
{
tarjan(j);
p[j] = u; //合并j与u的所在集合
}
}
for(auto t:query[u])
{
int j = t.first,id = t.second;
if(st[j] == 2)
{
int anc = find(j); //求出的公共祖先
res[id] = dist[u] + dist[j] - 2*dist[anc];
}
}
st[u] = 2; //标记已回溯
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
}
for (int i = 0; i < n-1; i ++ )
{
scanf("%d%d%d", &x, &y,&k);
add(x,y,k);
add(y,x,k);
}
for (int i = 0; i < m; i ++ )
{
scanf("%d%d", &x, &y);
query[x].push_back({y,i});//注意:两个都要存
query[y].push_back({x,i});
}
dfs(1,-1);
tarjan(1);
for(int i=0;i<m;i++)
{
cout<<res[i]<<endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)