目录
树的直径 C++题解:树的直径_c++树的直径-CSDN博客
BFS
八数码(模板)
#include<bits/stdc++.h>
using namespace std;
int bfs(string state)
{
queue<string> q; //定义队列
unordered_map<string,int> d; //通过哈希表来让字符串变化时和移动的距离数值关联
q.push(state); //将字符串入队
d[state]=0; //将初始状态的字符串的哈希值设定为1
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; //定义四个方向向量
string end="12345678x"; //定义宽度优先搜素的终止状态
while(q.size()) //循环终止状态
{
auto t=q.front(); //将队列中存着的字符串赋值给t
q.pop(); //队头元素弹出
if( t==end ) return d[t]; //如果当前字符串等于终止状态搜索结束返回该字符串对应的哈希值
//此处的哈希函数值对应于字符串移动的次数
int distance = d[t]; //定义一个临时变量distance存储形成t字符串当前的移动次数
int k = t.find('x'); //k表示'x'字符在字符串当前的下标
int x = k/3,y = k%3; //由于字符串当前是一维的将一维下标转化为二维坐标
for(int i=0;i<4;i++) //分别遍历四个方向
{
int a=x+dx[i],b=y+dy[i]; //将下一个搜索位置的x,y坐标表示
if(a>=0&&a<3&&b>=0&&b<3) //当二维坐标满足位于3X3矩阵中时
{
swap(t[a*3+b],t[k]); //将字符串中的搜索位置与字符'x'交换
if(!d.count(t)) //如果当前的字符串的哈希值为0
{
d[t]=distance+1; //将该字符串对应的哈希值在原字符串对应的哈希值基础上加1
q.push(t); //将该字符串入队
}
swap(t[a*3+b],t[k]); //恢复现场,返回位置判断其他方向
}
}
}
return -1; //如果无法移动到终止位置返回-1
}
int main()
{
char s;
string state;
for(int i=0;i<9;i++)
{
cin>>s;
state+=s; //逐个输入字符串
}
cout<<bfs(state)<<endl; //输出宽度优先搜索的数值
return 0;
}
卡片换位(同上)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include <queue>
using namespace std;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int a, b; //记录起始A、B下标
int bfs(string start)
{
//定义bfs队列和dist数组
queue<string> q;
unordered_map<string, int> d;
//初始化队列和dist数组
q.push(start);
d[start] = 0;
while (q.size())
{
//取队头元素
auto t = q.front();
q.pop();
//记录当前状态的距离,如果是最终状态则返回距离
int distance = d[t];
if (t.find('A') == b && t.find('B') == a) return distance;
//查询 ' '在字符串中的下标,然后转换为在矩阵中的坐标
int k = t.find(' ');
//将一维坐标转换为二维
int x = k / 3, y = k % 3;
for (int i = 0; i < 4; i ++ )
{
//转移后 ' '的坐标
int xx = x + dx[i], yy = y + dy[i];
//若当前坐标没有越界
if (xx >= 0 && xx < 2 && yy >= 0 && yy < 3)
{
//转移状态
swap(t[k], t[xx * 3 + yy]);
//如果当前状态没被遍历过 记录距离 入队
if (!d.count(t))
{
d[t] = distance + 1;
q.push(t);
}
//还原状态,为下一种情况做准备
swap(t[k], t[xx * 3 + yy]);
}
}
}
}
int main()
{
string start;
for (int i = 0; i < 2; i ++ )
{
string tmp;
getline(cin, tmp);//*********** 读取带空格的字符串,整行读取 *********八数码中空格不算字符 而此题空格算字符
start += tmp;
}
for (int i = 0; i < 6; i ++ )
{
if (start[i] == 'A') a = i;
if (start[i] == 'B') b = i;
}
cout << bfs(start) << endl;
return 0;
}
青蛙跳杯子(同上)
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
queue<string> q;
unordered_map<string,int> d;
int dx[6]={-2,-1,1,2,-3,3};
int n;
//string start,endss;
char start[20];
char endss[20];
int bfs()
{
q.push(start);
d[start]=0;
while(q.size())
{
auto t=q.front();
q.pop();
int distance=d[t];//1
if(t==endss) return distance;
int pos=t.find('*');
for(int i=0;i<6;i++)
{
int nx=pos+dx[i];
if(nx>=0&&nx<n)
{
swap(t[nx],t[pos]);
if(d.count(t)==0) //
{
q.push(t);
d[t]=distance+1;//1
}
swap(t[pos],t[nx]);
}
}
}
return -1;
}
int main()
{/**********************
scanf("%s") 不能用于读string a
*/
// cin>>start>>endss;
scanf("%s",start);
scanf("%s",endss);
/*getline(cin,start);
getline(cin,endss);*/
//scanf %s 不行
n = strlen(start);//
cout << bfs() << endl;
return 0;
}
链表
整数删除(双向链表+堆)
AcWing 4961. 整数删除 | 堆 | 双向链表 - AcWing 题解
//双向链表 堆
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 5e5 + 10;
ll v[N], l[N], r[N];//v:权值
void del(int x)
{
r[l[x]] = r[x], l[r[x]] = l[x];
v[l[x]] += v[x], v[r[x]] += v[x];
}
int main ()
{
int n, k; cin >> n >> k;
r[0] = 1, l[n + 1] = n; //0和n+1 情况 r[0] 此时1是头,后面可能指针变r[0]不一定等于1 但r[0]是头
//l[1]=0,r[n]=n+1; 错
priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> h;//堆 找min
for (int i = 1; i <= n; i ++)
cin >> v[i], l[i] = i - 1, r[i] = i + 1, h.push({v[i], i});
while (k --)
{
auto p = h.top(); h.pop();
if (p.first != v[p.second]) h.push({v[p.second], p.second}), k ++;//没删,不应该占操作数k, while k--了,要++
else del(p.second);
}
int head = r[0];//r[0]***头不一定是1了,双向链表,位置不变,但指针已经变了,已经删了****
while (head != n + 1) //合法
{
cout << v[head]<< " ";
head = r[head];//链表往后走
}
return 0;
}
消除游戏
//快速删除:双链表
//边缘字母只可能在左右两边发生变化
//q:备选集合,w:待删集合
//以后每次从备选删的里找。每次删都会改变被删,所以每次清空被删
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N=1000010;
char s[N];//并未真正删除s中的元素,只是逻辑修改了下标
int l[N],r[N];
int st[N];//标记下表是否被删除
vector<int> q,w;//q表示备删的元素,w表示待删元素
void insert(int temp)//insert函数
{
if(st[temp]==0)//不能已经删过了 有可能两个元素在不同情况里都被删(判重) 如:00xx
{
st[temp]=1;
w.push_back(temp);
}
}
void filterfun()
{
w.clear();//清空要删除的元素
for(int t:q)//遍历备删元素,找到符合条件的待删元素
{
int a=l[t],b=t,c=r[t];
if(s[a]==s[b]&&s[b]!=s[c]&&s[c]!='#') //如果是#aa#,那么b和c就不能插入到待删元素,多举例子就懂了
{
insert(b);
insert(c);
}
if(s[a]!=s[b]&&s[b]==s[c]&&s[a]!='#')
{
insert(a);
insert(b);
}
}
}
int main()
{
scanf("%s",s+1);
int n=strlen(s+1);
s[0]='#',s[n+1]='#';//为数组设置边界
for(int i=1;i<=n;i++)//双链表初始化,注意不是循环双链表,与基础课中的区分
{
l[i]=i-1;
r[i]=i+1;
q.push_back(i);//初始化备删元素
}
r[0]=1;
l[n+1]=n;
while(1)//这里数组的长度远小于2的64次方,所以一定在数组长度的时间范围内退出循环,不会TLE
{
filterfun();//找到待删的下标
if(w.empty()) break;//表示没有待删的元素退出循环
q.clear();//每退出一次查找,要清空备选
for(int t:w)//删元素 并找备删元素
{
int a=l[t],b=t,c=r[t];
if(a!=0&&st[a]==0&&(q.empty()||a!=q.back())) q.push_back(a);//备删元素不可以是下标0,且该元素不可以被删,且不可以重复插入
if(c!=n+1&&st[c]==0)q.push_back(c);
r[a]=c;
l[c]=a;
}
}
if(r[0]==n+1) puts("EMPTY");//已经没有字符
else{
for(int i=r[0];i!=n+1;i=r[i])
cout<<s[i];
}
return 0;
}
打印大X
#include<iostream>
using namespace std;
char g[2010][2010];//len=m+n-1=2010
int main()
{
int m,n;
cin>>m>>n;
int len=n+m-1;//长度*******************
for(int i=0;i<(n+1)/2;i++)//上半边
{
for(int j=0;j<len;j++) g[i][j]='.';//先一行赋初值.,下面改*
int s1=i,s2=(len-1-i);
for(int j=0;j<m;j++)//左右均4个*
{
//if(s1>s2) break;
g[i][s1++]=g[i][s2--]='*';//一行左右同时*
}
}
for(int i=0;i<(n+1)/2;i++)//打印上半边
{
for(int j=0;j<len;j++) cout<<g[i][j];
cout<<endl;
}
for(int i=(n+1)/2-2;i>=0;i--)//对称 少一行
{
for(int j=0;j<len;j++) cout<<g[i][j];
cout<<endl;
}
return 0;
}
打印十字图(模拟,看图)
#include<stdio.h>
#define N 1000
int main(){
int a,b,c=0;
char aa[N][N];
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
aa[i][j]='0';//数组全部初始化为0,也可以用memset
}
}
scanf("%d",&a);//接受需要打印的层数
b=1+a*4+4;
for(int i=b/2-2;i<=b/2+2;i++)aa[i][b/2]='$';
for(int i=b/2-2;i<=b/2+2;i++)aa[b/2][i]='$';//初始化最内层的十字架
while(c<a)
{
for(int i=0;i<b;i++)
{
for(int j=0;j<b;j++)
{
if(aa[i][j]=='0'&&((aa[i-1][j]=='$'||aa[i][j-1]=='$'||aa[i+1][j]=='$'||aa[i][j+1]=='$'||aa[i-1][j-1]=='$'||aa[i+1][j+1]=='$'||aa[i-1][j+1]=='$'||aa[i+1][j-1]=='$')))
{
aa[i][j]='.';//采用洪泛反复循环,由内向外$和.交替生成
}
}
}
for(int i=0;i<b;i++)
{
for(int j=0;j<b;j++)
{
if(aa[i][j]=='0'&&((aa[i-1][j]=='.'||aa[i][j-1]=='.'||aa[i+1][j]=='.'||aa[i][j+1]=='.'||aa[i-1][j-1]=='.'||aa[i+1][j+1]=='.'||aa[i+1][j-1]=='.'||aa[i-1][j+1]=='.')))
{
aa[i][j]='$';//采用洪泛反复循环,由内向外$和.交替生成
}
}
}
c++;
}
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
aa[i][j]='.';
}
for(int j=b-1;j>b-3;j--)
{
aa[i][j]='.';//把四个角上的符号变成点
}
}
for(int i=b-1;i>b-3;i--)
{
for(int j=0;j<2;j++)
{
aa[i][j]='.';
}
for(int j=b-1;j>b-3;j--)
{
aa[i][j]='.';//把四个角上的符号变成点
}
}
for(int i=0;i<b;i++)
{
for(int j=0;j<b;j++)
{
printf("%c",aa[i][j]);//输出十字图
}
printf("\n");
}
}
核桃的数量 (最大公因数,最小公倍)
#include<iostream>
using namespace std;
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
int main()
{
int a,b,c;
cin>>a>>b>>c;
int num1=a*b/gcd(a,b);
int num2=num1*c/gcd(num1,c);
cout<<num2;
}
或遍历
航班时间
//去乘起飞时间+航行时间+时差=去乘降落时间
//回程起飞时间+航行时间-时差=回程降落时间
//求:航行时间
//航行时间=(去乘降落时间-去乘起飞时间+回程降落时间-回程起飞时间)/2
#include<bits/stdc++.h>
using namespace std;
int getTime(void)
{
int h1,m1,s1,h2,m2,s2,d=0;
scanf("%d:%d:%d %d:%d:%d (+%d)",&h1,&m1,&s1,&h2,&m2,&s2,&d);
int time=d*24*3600+h2*3600+m2*60+s2-(h1*3600+m1*60+s1);
return time;
}
int main()
{
int t;
scanf("%d",&t);
for(int i = 0; i < t; i++)
{
int time1=getTime();
int time2=getTime();
int t=(time1+time2)/2;
printf("%02d:%02d:%02d\n", t/3600, t/60%60, t%60);
}
return 0;
}
三国游戏 (贪心)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int n;
int A[N], B[N], C[N], W[N];
int check(int x[], int y[], int z[]) {
for (int i = 1; i <= n; i++) {
W[i] = x[i] - y[i] - z[i];
}
sort(W + 1, W + n + 1, greater<int>());
int res = -1;
LL sum = 0;
for (int i = 1; i <= n; i++) {
sum += W[i]; //前缀和
if (sum > 0) res = i;
else break;
}
return res;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> A[i];
for (int i = 1; i <= n; i++) cin >> B[i];
for (int i = 1; i <= n; i++) cin >> C[i];
int ans = max({check(A, B, C), check(B, A, C), check(C, A, B)});
cout << ans;
return 0;
}
耐摔指数 dp
//从每个厂家抽样3部手机参加测试
#include<iostream>
using namespace std; //i手机,j层楼
int dp[5][10010];
int main()
{
int n;
cin>>n;
for(int i=1;i<=3;i++)
{
for(int j=0;j<=n;j++) dp[i][j]=j;
}
for(int i=2;i<=3;i++) //手机
{
for(int j=1;j<=n;j++) //楼
{
for(int k=1;k<=j;k++) //中间情况
{
dp[i][j]=min(dp[i][j],max(dp[i-1][k-1],dp[i][j-(k+1)+1]) +1 ); //+1 :+k的那个 k-1
//max(dp[i-1][k-1],dp[i][j-(k+1)+1]) +1 最坏运气
}
}
}
cout<<dp[3][n];
}
缩位求和
248×15=3720248×15=3720
把乘数和被乘数分别逐位求和,如果是多位数再逐位求和,直到是 11 位数,得
输入:
一个由数字组成的串,表示 n� 位数。
输出
一位数,表示反复逐位求和的结果。
2 + 4 + 8 = 14 ==> 1 + 4 = 5;
1 + 5 = 6
5 * 6
#include<iostream>
#include<cstring>
using namespace std;
char a[1010];
int b[1010];
int main()
{
long long ans=0,num=0;
scanf("%s",a);
for(int i=0;i<strlen(a);i++)
{
b[i]=a[i]-'0';
num+=b[i]; //第一层
}
while(num>=10)
{
ans=0;
while(num)
{
ans+=num%10;//新的和
num/=10;
}
num=ans; //判断新的和是否>=10
}
cout<<ans;//最后num=ans
}
交换瓶子
#include<iostream>
using namespace std;
int a[100010];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(a[i]!=i)
{
for(int j=i+1;j<=n;j++)
{
if(a[j]==i)
{
int t=a[i];
a[i]=a[j];
a[j]=t;
ans++;
break;
}
}
}
}
cout<<ans<<endl;
}
移动距离
X星球居民小区的楼房全是一样的,并且按矩阵样式排列。
其楼房的编号为 1,2,3…1,2,3…
当排满一行时,从下一行相邻的楼往反方向排号。
比如:当小区排号宽度为 66 时,开始情形如下:
1 2 3 4 5 6
12 11 10 9 8 7
13 14 15 .....
我们的问题是:已知了两个楼号 m� 和 n�,需要求出它们之间的最短移动距离(不能斜线方向移动)。
输入共一行,包含三个整数 w,m,n�,�,�,w� 为排号宽度,m,n�,� 为待计算的楼号。
输出一个整数,表示 m,n�,� 两楼间最短移动距离。
1≤w,m,n≤100001≤�,�,�≤10000,
6 8 2
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
int w,m,n;
cin>>w>>m>>n;
int hang1=(m-1)/w+1;
int hang2=(n-1)/w+1;
int ans;
int lie1,lie2;
if(hang1%2==0)
{
lie1=w-m%w+1;
}
else
{
if(m%w) lie1=m%w;
else lie1=w;
}
if(hang2%2==0)
{
lie2=w-n%w +1;
}
else
{
if(n%w) lie2=n%w;
else lie2=w;
}
ans=abs(hang1-hang2)+abs(lie1-lie2);
cout<<ans;
}
等差数列
数学老师给小明出了一道等差数列求和的题目。
但是粗心的小明忘记了一部分的数列,只记得其中 N� 个整数。
现在给出这 N� 个整数,小明想知道包含这 N� 个整数的最短的等差数列有几项?
输入的第一行包含一个整数 N�。
第二行包含 N� 个整数 A1,A2,⋅⋅⋅,AN�1,�2,···,��。(注意 A1∼AN�1∼�� 并不一定是按等差数
列中的顺序给出)
输出一个整数表示答案。
2≤N≤1000002≤�≤100000,
0≤Ai≤1090≤��≤109
5
2 6 4 10 20
10
包含 2、6、4、10、202、6、4、10、20 的最短的等差数列是 2、4、6、8、10、12、14、16、18、202、4、6、8、10、12、14、16、18、20。
//各差值最大公因数 是本题最大公差
#include<iostream>
#include<algorithm>
using namespace std;
int a[100010];
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;//********
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n);
int gd=0; //***如:1 1 1 0与x的最大公因数:x 1与x最大公因数:1
for(int i=0;i<n-1;i++)
{
gd=gcd(gd,a[i+1]-a[i]);//若a[i]-a[0] 范围太大效果不好
}
if(gd==0) cout<<n<<endl;
else
{
int ans=(a[n-1]-a[0])/gd+1 ;
cout<<ans<<endl;
}
}
错误票据
刷题统计
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
LL a, b, n, ret;
cin >> a >> b >> n;
ret = n / (a * 5 + b * 2) * 7;
n -= (a * 5 + b * 2) * ret / 7;
for (int i = 0; i < 5 && n > 0; i ++) ret ++, n -= a;
for (int i = 0; i < 2 && n > 0; i ++) ret ++, n -= b;
cout << ret << endl;
return 0;
}
日志统计
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010; //根据题目数据范围,定N大小,此题则主要根据N,ts,id来确定的
typedef pair<int, int> PII; //定义一个PII类型以及一对int整型用于存储ts和id
#define x first //为了代码简洁,则分别把first, second 宏定义为x, y
#define y second
int n, d, k;
int cnt[N]; //用于存储每个id号获得赞数,所以后面代码为cnt[t] ++;
bool st[N]; //用于存储每个帖子是否是热帖,所以用bool类型
PII logs[N];//用于存储日志ts, id
int main()
{
scanf("%d %d %d", &n, &d, &k);
for(int i = 0; i < n; i ++) scanf("%d %d\n", &logs[i].first, &logs[i].second);
sort(logs, logs + n);//以第一个参数排序
for(int i = 0, j = 0; i < n; i ++)//双指针算法,i在前,j在后
{
int t = logs[i].second;//把每个获赞的帖子id存入t
cnt[t] ++;//获得一个赞,所以此刻 ++;
while(logs[i].first - logs[j].first >= d)//如果俩个帖子时间相差超过d,说明该赞无效
{
cnt[logs[j].second] --;//所以此刻--;
j ++;//要把指针j往后,否则死循环
}
if(cnt[t] >= k) st[t] = true; //如果该id贴赞超过k,说明是热帖
}
for(int i = 0; i < 100000; i ++)
{
if(st[i])//如果为真,则把热帖的id打印出来
cout << i << endl;
}
return 0;
}
后缀表达式
详细解答:AcWing 1247. 后缀表达式 最详细题解!!! - AcWing
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
const int N=2e5+10;
int a[N];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n+m+1;i++) scanf("%d",&a[i]);
LL res=0;
if(m==0) //特判,如果没有-号,就把序列全部加起来
{
for(int i=0;i<n+m+1;i++) res+=a[i];
}
else
{
sort(a,a+n+m+1); //排序
res-=a[0]; //减去最小的数
res+=a[n+m]; //加上最大的数
for(int i=1;i<n+m;i++) res+=abs(a[i]); //加上序列中间的数的绝对值
}
printf("%lld",res);
return 0;
}
连号区间数
注意题目给出的就是1~n的某个排列
#include<iostream>
using namespace std;
int a[100010];
int main()
{
int n;
scanf("%d",&n);
int min,max;
for(int i=1;i<=n;i++) cin>>a[i];
int ans=0;
for(int i=1;i<=n;i++)
{
min=100000,max=0;
for(int j=i;j<=n;j++)
{
if(a[j]>max) max=a[j];
if(a[j]<min) min=a[j];
if((max-min)==(j-i)) ans++;
}
}
cout<<ans<<endl;
}
统计 子矩阵
二维 变一维
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 5e2+3;
int n, m, k;
int a[N][N];
int main(){
ios::sync_with_stdio(false);
cin >> n >> m >> k;
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
cin >> a[i][j];
a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
}
}
ll ans = 0;
for(int i=1; i<=m; i++){
for(int j=i; j<=m; j++){
for(int s = 1, t = 1; t <= n; t ++ ){
while(s <= t && a[t][j] - a[s - 1][j] - a[t][i - 1] + a[s - 1][i - 1] > k) s ++ ;
if(s <= t) ans += t - s + 1;
}
}
}
cout << ans << '\n';
}
修剪灌木
从左到右从右到左一个循环 每棵树都被剪两次
其中第i棵树两次被剪的间隔 要么是左边的i-1棵树 要么是右边的n-i棵树 被剪两次
故最高高度=2*max(m-i,i-1)
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++)cout<<2*max(n-i,i-1)<<endl;
}
分巧克力
二分 check
儿童节那天有 K� 位小朋友到小明家做客。
小明拿出了珍藏的巧克力招待小朋友们。
小明一共有 N� 块巧克力,其中第 i� 块是 Hi×Wi��×�� 的方格组成的长方形。
为了公平起见,小明需要从这 N� 块巧克力中切出 K� 块巧克力分给小朋友们。
切出的巧克力需要满足:
- 形状是正方形,边长是整数
- 大小相同
例如一块 6×56×5 的巧克力可以切出 66 块 2×22×2 的巧克力或者 22 块 3×33×3 的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?
#include<iostream>
using namespace std;
int h[100010],w[100010];
int n,k;
bool check(int mid)
{
int sum=0;
for(int i=0;i<n;i++)
{
sum+=(h[i]/mid)*(w[i]/mid);
if(sum>=k) return 1;//可放里面,已经满足条件提前结束
}
return 0;
}
int main()
{
cin>>n>>k;
for(int i=0;i<n;i++)
{
cin>>h[i]>>w[i];
}
int l=1,r=1e5;//边长不超过1e5
int mid;
while(l<r) //等于时结束
{
mid=(l+r+1)/2;
if(check(mid)) l=mid; //找到最大的,mid可以,要看右边的行不行,
else r=mid-1; //mid不行 左边从Mid-1
}
cout<<l; //r也行 mid 不行
}
乘法表
p 进制的乘法表就是多了一次进制转换。
写一个函数I2P,输出 十 进制的数,返回它对应的 p 进制下的结果。
字符串 好弄
#include<iostream>
using namespace std;
int p; //p进制
string jin(int a)
{
string res;
while(a)
{
int t=a%p;
if(t<10) res=(char)(t+'0')+res; //字符串 加数不能反过来 ‘a'+'b'=ab
//先出的余数在最右边,每出一个余数都放最左边
else res=(char)('A'+t-10)+res;
a/=p;
}
return res;
}
int main()
{
cin>>p;
for(int i=1;i<p;i++)
{
for(int j=1;j<=i;j++)
{
cout<<jin(i)<<"*"<<jin(j)<<"="<<jin(i*j)<<" ";
}
cout<<endl;
}
}
蚂蚁感冒
先分两种大的情况,就是第一个感冒的蚂蚁向左还是向右
如果向右, 那么它右面向左走的一定会传染,右面向右走的一定不会传染
它左面向左走的一定不会传染,它左面向右走的会被它右面向左走的感染
如果向左,与上同理
交换
#include <iostream>
#include <vector>
#include <string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[100010];
int cmp(int a,int b)
{
return abs(a)<abs(b);
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
int temp=a[0];
sort(a,a+n,cmp);
int xia;
for(int i=0;i<n;i++)
{
if(a[i]==temp)
{
xia=i;
}
}
int num=1;//
if(temp>0)
{
int f=0;
for(int i=xia+1;i<n;i++)
{
if(a[i]<0)
{
num++;
f=1;
}
}
if(f)
{
for(int i=0;i<xia;i++)
{
if(a[i]>0)
{
num++;
}
}
}
}
else
{
int f=0;
for(int i=0;i<xia;i++)
{
if(a[i]>0)
{
num++;
f=1;
}
}
if(f)
{
for(int i=xia+1;i<n;i++)
{
if(a[i]<0)
{
num++;
}
}
}
}
cout<<num<<endl;
}
兰顿蚂蚁(模拟)
注意思路!write by yourself
#include<iostream>
using namespace std;
int Map[101][101];
int m, n;//正方形格子的行数和列数
int x, y,k;//x,y为蚂蚁所在的行号和列号,k为蚂蚁走的步数
char s;//蚂蚁头的朝向,上下左右UDLR
void Go() {
if (Map[x][y] == 1) {//1为黑格,右转变白格
Map[x][y] = 0;
if (s == 'U') {
s = 'R';
y++;
}
else if (s == 'R') {
s = 'D';
x++;
}
else if (s == 'D') {
s = 'L';
y--;
}
else if (s == 'L') {
s = 'U';
x--;
}
}
else {//0为白格,左转变黑格
Map[x][y] = 1;
if (s == 'U') {
s = 'L';
y--;
}
else if (s == 'R') {
s = 'U';
x--;
}
else if (s == 'D') {
s = 'R';
y++;
}
else if (s == 'L') {
s = 'D';
x++;
}
}
}
int main() {
cin >> m >> n;
for (int i = 0; i < m; i++) {//初始化地图
for (int j = 0; j < n; j++) {
cin >> Map[i][j];
}
}
cin >> x >> y >> s >> k;
while (k--) {
Go();
}
cout << x << " " << y << endl;
}
翻硬币
//模拟法 从前往后翻硬币
#include<iostream>
using namespace std;
int main()
{
int cnt=0;
string a,b;
cin>>a>>b;
for(int i=0;i<a.size()-1;i++)
{
if(a[i]!=b[i])
{
cnt++;
if(a[i]=='o') a[i]='*';
else a[i]='o';
if(a[i+1]=='o') a[i+1]='*'; //翻转第一组 与第二组对比
else a[i+1]='o';
}
}
cout<<cnt;
}
子串简写
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;
char str[N];
int s[N];
int n, k;
char st, ed;
int main()
{
scanf("%d\n%s %c %c", &k, str + 1, &st, &ed);
n = strlen(str + 1);
LL res = 0;
for (int i = 1, t = 0; i <= n; i++)
{
if (str[i] == st) t++;//
s[i] = t;
if (i >= k && str[i] == ed) res += s[i - k + 1];
}
printf("%lld\n", res);
return 0;
}//前缀和
走迷宫bfs(模板) 队列
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N=110;
int n,m;
int g[N][N],d[N][N];
int bfs()
{
queue<PII> q;//队列
d[0][0]=0;
q.push({0,0});
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
while(q.size())
{
PII t=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int x=t.first+dx[i],y=t.second+dy[i];
if(x>=0&&x<n&&y>=0&&y<m&&g[x][y]==0&&d[x][y]==0 )//写在for循环里面
{
d[x][y]=d[t.first][t.second]+1;
// q{x,y}=1;
q.push({x,y});
}
}
}
cout<< d[n-1][m-1];
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++) cin>>g[i][j];
}
bfs();
return 0;
}
全球变暖 bfs
//一个岛屿里有>=1个土地没接触到水就不算 完全浸湿
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define x first
#define y second
using namespace std;
char arr[1010][1010];
bool finddao[1010][1010];
int N;
bool bfs(pair<int,int>s1){
int sum=0;//计算不被海水侵蚀的路地块个数
bool mark;//用于判断十分被侵蚀
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};//初始化偏移量
queue<pair<int,int>>dui;
dui.push(s1);
while(dui.size()){
auto s2=dui.front();
dui.pop();
mark=1;
for(int i=0;i<4;i++){
int x1=s2.x+dx[i],y1=s2.y+dy[i];
if(x1<0||x1>=N||y1<0||y1>=N)continue;
if(arr[x1][y1]=='.'){mark=0;continue;}
if(finddao[x1][y1])continue;
finddao[x1][y1]=true;
dui.push(make_pair(x1,y1));
}
if(mark==1)sum++;
}
if(sum>=1)//如果岛上有不被侵蚀的路地块那么这岛能存活
return 1;
else //这岛寄了
return 0;
}
int main(){
int allisland=0,lifeisland=0;
pair<int,int> s1;
cin>>N;
for(int i=0;i<N;i++)cin>>arr[i];
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
if(arr[i][j]=='#'&&!finddao[i][j]){
s1={i,j};
allisland++;//计算所有岛的数目
lifeisland+=bfs(s1);//计算能存活的岛
}
}
}
cout<<allisland-lifeisland;//用所有的岛数目减能留下的岛数目剩下就是沉下的岛
return 0;
}
穿越雷区
/*bfs模板*/
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include <cstring>
using namespace std;
const int N=110;
bool st[N][N];
char g[N][N];
typedef pair<int,int> PII;
int n;
int f[N][N];
void dfs(int x,int y)
{
f[x][y]=0;//***********
int dx[4]={-1,1,0,0},dy[4]={0,0,1,-1};
st[x][y]=true;
queue<PII> q;
q.push({x,y});
while(q.size())
{
auto t=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int a=dx[i]+t.first,b=dy[i]+t.second;
if(a<0||a>=n||b<0||b>=n||st[a][b]||g[a][b]==g[t.first][t.second]) continue;
if(f[a][b]>f[t.first][t.second]+1)
{f[a][b]=f[t.first][t.second]+1;
q.push({a,b});//
st[a][b]=true;}//放不放{}里都行
}
}
}
int main()
{
cin>>n;
int ax,ay,bx,by;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cin>>g[i][j];
if(g[i][j]=='A') ax=i,ay=j;
if(g[i][j]=='B') bx=i,by=j;
}
}
memset(f,0x3f,sizeof f); //**************
dfs(ax,ay);
if(f[bx][by]==0x3f3f3f3f) cout<<"-1"<<endl;
else cout<<f[bx][by]<<endl;
}
dfs(模板)
九宫幻方
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 15;
int g[N], ans[N];
bool st[N], used[N]; //位置, 数字
int cnt;
void check()
{
int t[8] = {0};
t[0] = g[1] + g[2] + g[3];
t[1] = g[4] + g[5] + g[6];
t[2] = g[7] + g[8] + g[9];
t[3] = g[1] + g[4] + g[7];
t[4] = g[2] + g[5] + g[8];
t[5] = g[3] + g[6] + g[9];
t[6] = g[1] + g[5] + g[9];
t[7] = g[3] + g[5] + g[7];
for (int i = 0; i <= 7; i++) if (t[i] != 15) return;
cnt++;
for (int i = 1; i <= 9; i++) ans[i] = g[i];
}
void dfs(int x) // 第x个位置
{
if (cnt > 1) return;
if (x > 9)
{
check();
return;
}
if (st[x])//此位置已有数字
{
dfs(x + 1);
return;
}
for (int i = 1; i <= 9; i++)
{
if (!used[i])
{
g[x] = i;
used[i] = true;
dfs(x + 1);
used[i] = false;
}
}
}
int main()
{
for (int i = 1; i <= 9; i++)//初始化
{
cin >> g[i];
if (g[i])
{
st[i] = true; //
used[g[i]] = true; //此数字用过了
}
}
dfs(1);
if (cnt == 1)
{
for (int i = 1; i <= 9; i++)
{
cout << ans[i] << ' ';
if (i % 3 == 0) cout << endl;
}
}
else cout << "Too Many";
return 0;
}
排列数字 (堆)
输出1~n所用排列
123 132 213 231 312 321
#include<iostream>
using namespace std;
const int N=10;
int path[N];
bool st[10];
int n;
void dfs(int u)
{
if(u==n) ///到最后一个数字了
{
for(int i=0;i<n;i++ ) cout<<path[i]<<" "; //输出path数组(下标从0)
cout<<endl;
return;
}
for(int i=1;i<=n;i++)//找1~n里没被用过的数i
{
if(!st[i])
{
path[u]=i; //当前位置 i
st[i]=true; //i用过了
dfs(u+1); //下一层
st[i]=false; //回溯
}
}
}
int main()
{
cin>>n;
dfs(0);
return 0;
}
小朋友的崇拜圈(真题)
a[i]:第i个人崇拜的人
AcWing 3170. 小朋友崇拜圈 - AcWing 题解
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int nextC[N]; //每个孩子最崇拜的孩子
int dfn[N]; //每个点的时间戳
int n, ans, idx;
int dfs(int u, int start)
{
dfn[u] = ++idx;
if (dfn[nextC[u]] && dfn[nextC[u]] >= start) //有可能时自环所以时>=
return dfn[u] - dfn[nextC[u]] + 1;
if (dfn[nextC[u]] && dfn[nextC[u]] < start) return 0;
return dfs(nextC[u], start);
}
int main()
{
cin>>n;
for(int i = 1; i <= n; ++i) cin >> nextC[i];
for(int i = 1; i <= n;++i)
{
if (!dfn[i])
ans = max(ans, dfs(i, idx + 1));
}
cout<<ans;
return 0;
}
飞机降落(真题)
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=20;
bool st[N];
int n;
struct plane
{
int t,d,l;
}p[N];
bool dfs(int u,int last)
{
if(u==n)
{
return true;
}
for(int i=0;i<n;i++)
{
int t=p[i].t,d=p[i].d,l=p[i].l;
if(!st[i]&&(t+d)>=last)
{
st[i]=true;
if( dfs(u+1,max(last,t)+l)) return true;
//不回溯(不是排列数字,数字要重复用)
//这个飞机就排一次
st[i]=false; //回溯,不满足的回去,不能弄别的回不来了
}
}
return false;
}
int main()
{
int t;
cin>>t;
while(t--)
{
memset(st,0,sizeof st);
cin>>n;
for(int i=0;i<n;i++)
{
int t,d,l;
cin>>t>>d>>l;
p[i].t=t,p[i].d=d,p[i].l=l;
}
if(dfs(0,0)) puts("YES");
else puts("NO");
}
}
正则表达式(真题)********
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std ;
int k ;
string str ;
int dfs(){
int res = 0 ;
while(k<str.size()){
if(str[k] == '('){
k ++ ;
res += dfs() ;
k ++ ;
}else if(str[k] == ')'){
// k ++ ;
break ;
}else if(str[k] == '|'){
k ++ ;
res = max(res,dfs()) ;
}else{
k ++ ;
res ++ ;
}
}
return res ;
}
int main(){
cin >> str ;
cout << dfs() << endl ;
return 0 ;
}
大臣的旅费(真题)+树的直径********
树的直径 C++题解:树的直径_c++树的直径-CSDN博客
#include <iostream>
#include <algorithm>
//无环图 首都到其他仅(不重复)一条路
#include <vector>
using namespace std;
typedef long long LL;
const int N=1e5+10;
struct Node
{
int id,w;
};
vector<Node> node[N];
int n;
int dist[N];
void dfs(int u,int father,int distance)//u:此点
{
dist[u]=distance; //求dist
//求各点到该点u的距离
for(auto nd:node[u]) //u->nd
if(nd.id!=father) //换点了,可
dfs(nd.id,u,dist[u]+nd.w);//顺着树节点往下走
}
int main()
{
cin>>n;
for(int i=0;i<n-1;i++)
{
int p,q,d;
cin>>p>>q>>d;
node[p].push_back({q,d});//node[i].id:i连向的点
node[q].push_back({p,d});
}
/*任意取一点,计算其他点距离该点的最远距离
这里的任意一点取的是,树根
1表示从第一个节点开始
-1表示该结点的前一个结点(父节点)为-1,即无
0表示目前该结点到任意取得结点的距离,初始化为0*/
dfs(1,-1,0);
//找到距离根节点最远的叶子节点
int u=1;
for(int i=1;i<=n;i++)
if(dist[i]>dist[u]) //dist最大
u=i;
//dfs:其他结点距离该叶子节点的距离
dfs(u,-1,0);//初始化
for(int i=1;i<=n;i++)
if(dist[i]>dist[u])//dist最大
u=i;
cout<<(10*dist[u]+dist[u]*(dist[u]+1LL)/2)<<endl;
return 0;
}
spfa
作物杂交
//https://www.acwing.com/video/4647/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
//h:邻接表 dist[x]迄今为止生成x需要的时间 w[x]:一个x成熟的时间
using namespace std;
const int N = 2010, M = 200010;
int n, m;
int h[N], e[M], w[N], target[M], ne[M], idx;
int dist[N];
queue<int> q;
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, target[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void spfa()
{
while (q.size())
{
int x = q.front();
q.pop();
st[x] = false;//标记节点x不在队列中
for (int i = h[x]; i != -1; i = ne[i])//可扩展min
{
int y = e[i], z = target[i];
if (dist[z] > max(dist[x], dist[y]) + max(w[x], w[y]))
{
dist[z] = max(dist[x], dist[y]) + max(w[x], w[y]);
if (!st[z])//节点z不在队列中
{
q.push(z);//加入队列
st[z] = true;
}
}
}
}
}
int main()
{
int k, T;
scanf("%d%d%d%d", &n, &m, &k, &T);
memset(h, -1, sizeof h);//
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);//从1
memset(dist, 0x3f, sizeof dist);//
while (m -- )
{
int x;
scanf("%d", &x);
dist[x] = 0;
q.push(x);
st[x] = true;
}
while (k -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
add(b, a, c);
}
spfa();
printf("%d\n", dist[T]);
return 0;
}
dp
砝码称重
AcWing 3417. 砝码称重(闫式dp/dfs) - AcWing 题解
/*每个砝码 不放 放左 右
*f[i][j]从前i个砝码中选 称重量为j 是否可以
*/
#include<iostream>
using namespace std;
const int N=1e5+10;
bool f[110][N];
int a[N];
int main()
{
f[0][0]=true;
int n;
cin>>n;
int sum=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=sum;j++)//从0开始 方案数
{
f[i][j]=f[i-1][j]||f[i-1][abs(j-a[i])]; //第i个 不选/左/右
//防止越界
if (j + a[i] <= sum) f[i][j] |= f[i - 1][j + a[i]];//或运算
}
}
int ans=0;
for(int i=1;i<=sum;i++) //答案中 称出重量为0的不算
{
if(f[n][i]==true) ans++;
}
cout<<ans<<endl;
}
松散子序列
/*dp:选或不选*/
#include<iostream>
#include<cstring>
//pi:不能连续选
using namespace std;
char str[1000010];
int f[1000010][2];
int main()
{
scanf("%s",str+1);
int n=strlen(str+1);
for(int i=1;i<=n;i++)
{
f[i][0]=max(f[i-1][0],f[i-1][1]);//不选
f[i][1]=f[i-1][0]+str[i]-'a'+1; //前一个只能不选
}
cout<<max(f[n][1],f[n][0]);
}
垒筛子******
地宫取宝
#include <bits/stdc++.h>
using namespace std;
const int N = 55, M = 15, mod = 1e9 + 7;
int n, m, c;
int a[N][N];
int f[N][N][M][M];
int main() {
cin >> n >> m >> c;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
a[i][j]++;
}
}
// 初始化
f[1][1][0][0] = f[1][1][1][a[1][1]] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int cnt = 0; cnt <= c; cnt++) {
for (int k = 0; k < M; k++) {
if (f[i][j][cnt][k]) {
// 不取 (i+1,j) 的物品,可以直接从 (i,j) 转移到 (i+1,j)
f[i + 1][j][cnt][k] = (f[i + 1][j][cnt][k] + f[i][j][cnt][k]) % mod;
// 不取 (i,j+1) 的物品,可以直接从 (i,j) 转移到 (i,j+1)
f[i][j + 1][cnt][k] = (f[i][j + 1][cnt][k] + f[i][j][cnt][k]) % mod;
// 还可以取物品
if (cnt + 1 <= c) {
// 取 (i+1,j) 的物品,从 (i,j) 转移到 (i+1,j)
if (a[i + 1][j] > k) {
f[i + 1][j][cnt + 1][a[i + 1][j]] = (f[i + 1][j][cnt + 1][a[i + 1][j]] + f[i][j][cnt][k]) % mod;
}
// 取 (i,j+1) 的物品,从 (i,j) 转移到 (i,j+1)
if (a[i][j + 1] > k) {
f[i][j + 1][cnt + 1][a[i][j + 1]] = (f[i][j + 1][cnt + 1][a[i][j + 1]] + f[i][j][cnt][k]) % mod;
}
}
}
}
}
}
}
int res = 0;
for (int i = 1; i < M; i++) {
res = (res + f[n][m][c][i]) % mod;
}
cout << res << '\n';
return 0;
}
画中漂流***
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long LL;
const int MOD = 1000000007;
const int N = 3010;
int dp[N][N]; // dp[i][j] : 时间i 剩余体力j 的方案数
LL res;
int d, t, m;
int main() {
cin >> d >> t >> m;
dp[0][m] = 1;
// i 是时间,power 是体力
for (int i = 1; i <= t; ++i) {
for (int power = 0; power <= m; ++power) {//power:剩的体力
// 通过时间和体力算出距离,用了多少体力就向上游了多少
// 原始距离 = d
// 当前时间 = i
// 当前剩余体力 = power
// 计算向上游长度 = (m - power)
// 计算向下漂流长度 = i - (m - power)
// 计算当前位置 = 原始距离 + 向上游长度 - 向下漂流长度
// 计算当前位置 = d + (m - power) - (i - (m - power));
int length = d + (m - power) - (i - (m - power));
// 不死
if (length > 0) {
//第i次不用体力 i=power -> i-1=power
dp[i][power] = (dp[i][power] + dp[i - 1][power]) % MOD;
//第i次用体力 i :power -> i-1次power+1
dp[i][power] = (dp[i][power] + dp[i - 1][power + 1]) % MOD;
}
}
}
// 要求必须要用完所有体力
cout << dp[t][0];
return 0;
}
数字三角形
/*Dp
* * *
/ / \
* * * * * *
\ \ /
* * * * * * * * *
/ \
* * * * * * * *
n为偶数时,最后一层落在的点一定在n/2或n/2+1
n为奇数时,最后一层落在的点一定在n/2+1(向左下走的次数与向右下走的次数相差不能超过 1)
f[i][j]表示所有从头开始往下走到第i层第j个的路径的最大值
*/
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 110;
int n;
int f[N][N];
int triangle[N][N];
int main()
{
scanf("%d",&n);
for(int i = 1; i<=n; i++)
for(int j = 1; j <=i; j++)//j<=i i***
scanf("%d",&triangle[i][j]);
for(int i = 1; i<= n; i++)
for(int j = 1; j <=i; j++)
f[i][j] = max(f[i-1][j],f[i-1][j-1]) + triangle[i][j];//max +
if(n % 2 == 0) printf("%d\n", max(f[n][n/2],f[n][n/2+1]));
else printf("%d\n", f[n][n/2+1]);
return 0;
}
最优包含
//注意:是操作
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
char a[1050],b[1050];
int f[1050][1050];
int main()
{
scanf("%s",a+1);
scanf("%s",b+1);
int n=strlen(a+1),m=strlen(b+1);
memset(f, 0x3f, sizeof f);//找min,先弄成最大的 顺序与下
f[0][0]=0;
for(int i=1;i<=n;i++)
{
f[i][0]=0;
for(int j=1;j<=m;j++)
{
f[i][j]=f[i-1][j];//要不弄成0x3f min一直=0
if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);//不用a[i],用a[i] 操作数
//a[i]=b[j] 不用操作
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);//不用a[i],用a[i] a[i]!=b[j] 用操作
}
}
cout<<f[n][m];
}
走方格dfs
在平面上有一些二维的点阵。
这些点的编号就像二维数组的编号一样,从上到下依次为第 11 至第 n� 行,从左到右依次为第 11 至第 m� 列,每一个点可以用行号和列号来表示。
现在有个人站在第 11 行第 11 列,要走到第 n� 行第 m� 列。
只能向右或者向下走。
注意,如果行号和列数都是偶数,不能走入这一格中。
问有多少种方案。
#include<bits/stdc++.h>
using namespace std;
int f[40][40];//f[i][j]从1,1到i,j方案数
int n,m;
void dfs()
{
f[1][1]=1;//从1,1到1,1的方案数:1(不用动的方案)
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(i==1&&j==1) continue;
if(i%2==0&&j%2==0) continue;
else f[i][j]=f[i-1][j]+f[i][j-1];//从上/左来
}
}
}
int main()
{
cin>>n>>m;
dfs();
cout<<f[n][m]<<endl;
return 0;
}
区间DP
石子合并(模板)
设有 N� 堆石子排成一排,其编号为 1,2,3,…,N1,2,3,…,�。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N� 堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 44 堆石子分别为 1 3 5 2
, 我们可以先合并 1、21、2 堆,代价为 44,得到 4 5 2
, 又合并 1、21、2 堆,代价为 99,得到 9 2
,再合并得到 1111,总代价为 4+9+11=244+9+11=24;
如果第二步是先合并 2、32、3 堆,则代价为 77,得到 4 7
,最后一次合并代价为 1111,总代价为 4+7+11=224+7+11=22。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
/*f(i,j):从i合并到j 需要的代价
i<k<j
f(i,j)=minf(i,k) minf(k+1,j)+数字总和
len:区间长度 j:区间右端点 i:区间左端点
k:左边堆的右端点 f(i,j) =左i~k 右k+1~j****
枚举长度、左端点*/
#include<iostream>
using namespace std;
const int N=400;
int f[N][N],s[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s[i];
s[i]+=s[i-1];
}
for(int len=2;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)//在len 约束下的j
{
int j=i+len-1;
f[i][j]=1e8;
for(int k=i;k<=j-1;k++)//k=i:左边只有一堆 k=j-1(左边这一堆最大到j-1,右边最少有一堆)****
{
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]); //代价的和 +号 合并左,右独立
}
}
}
cout<<f[1][n];
}
合并石子(真题)比上面多了颜色属性
在桌面从左至右横向摆放着 N� 堆石子。
每一堆石子都有着相同的颜色,颜色可能是颜色 00,颜色 11 或者颜色 22 中的其中一种。
现在要对石子进行合并,规定每次只能选择位置相邻并且颜色相同的两堆石子进行合并。
合并后新堆的相对位置保持不变,新堆的石子数目为所选择的两堆石子数目之和,并且新堆石子的颜色也会发生循环式的变化。
具体来说:两堆颜色 00 的石子合并后的石子堆为颜色 11,两堆颜色 11 的石子合并后的石子堆为颜色 22,两堆颜色 22 的石子合并后的石子堆为颜色 00。
本次合并的花费为所选择的两堆石子的数目之和。
给出 N� 堆石子以及他们的初始颜色,请问最少可以将它们合并为多少堆石子?
如果有多种答案,选择其中合并总花费最小的一种,合并总花费指的是在所有的合并操作中产生的合并花费的总和。
const int N = 5e2+10,INF=1e16;
//最后一句所说的为什么不是最小的,是因为可能这个区间没法合并成一块,所以此时的值是无穷大,因此我们要在后面再跑一次dp**************
int n;
int f[N][N][3]; //f[i][j][k]表示i-j这区间合并的石子的颜色,INF表示不合法
int s[N];
int c[N][N]; //c(i,j)表示 合并 i-j这区间后 的 最小的堆数
int w[N][N]; //w(i,j)表示 合并 i-j这区间后 的 最小的价值
void solve(){
cin>>n;
//初始化c数组
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
c[i][j]=j-i+1;
if(i!=j)w[i][j]=INF; //初始化价值
for(int k=0;k<3;k++) //初始化f数组
f[i][j][k]=INF;
}
}
//前缀和存数据,区间dp常见操作,方便后面取到区间的值
for(int i=1;i<=n;i++){
int x;
cin>>x;
s[i]=s[i-1]+x;
}
//初始化 显然当前长度为1的区间 颜色x 是合法的
for(int i=1;i<=n;i++){
int x;
cin>>x;
f[i][i][x]=0;
}
//区间dp
for(int len=1;len<=n;len++){ //长度
for(int l=1;l+len-1<=n;l++){ //左端点
int r=l+len-1; //右端点
for(int i=0;i<3;i++){ //枚举颜色的所有可能
int ans=INF; //记录合法情况的最小值
for(int j=l;j<r;j++){ //枚举中间的边界
if(f[l][j][i]!=INF&&f[j+1][r][i]!=INF){ //两边都是合法情况才允许合并
ans=min(ans,f[l][j][i]+f[j+1][r][i]); //记录最小值
}
}
if(ans!=INF){ //不为INF代表有合法情况
c[l][r]=1; //此时区间长度为1,因为变成一堆石子
f[l][r][(i+1)%3]=min(f[l][r][(i+1)%3],ans+s[r]-s[l-1]); //更新答案
//更新最小价值
w[l][r]=min(w[l][r],f[l][r][(i+1)%3]);
}
}
for(int j=l;j<r;j++){ //枚举中间的边界
if(c[l][r]>c[l][j]+c[j+1][r]){ //找到更小合并方式就更新 堆数 和 价值
c[l][r]=c[l][j]+c[j+1][r];
w[l][r]=w[l][j]+w[j+1][r];
}
else if(c[l][r]==c[l][j]+c[j+1][r]){ //相等的情况 就 选价值最小的
w[l][r]=min(w[l][r],w[l][j]+w[j+1][r]);
}
}
}
}
cout<<c[1][n]<<' '<<w[1][n];
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
}
合并果子(贪心)(haffman树)
/*q.top(),q.push() */
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int main()
{
int n;
cin>>n;
priority_queue<int,vector<int>,greater<int>> q;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
q.push(x);
}
long long res=0;
while(q.size()>1)
{
int a=q.top(); q.pop();
int b=q.top(); q.pop();
res+=a+b;
q.push(a+b);
}
printf("%lld\n",res);
return 0;
}
数组切分
已知一个长度为 N� 的数组:A1,A2,A3,…AN�1,�2,�3,…�� 恰好是 1∼N1∼� 的一个排列。
现在要求你将 A� 数组切分成若干个 (最少一个,最多 N� 个) 连续的子数组,并且每个子数组中包含的整数恰好可以组成一段连续的自然数。
例如对于 A={1,3,2,4}�={1,3,2,4},一共有 55 种切分方法:
- {1}{3}{2}{4}{1}{3}{2}{4}:每个单独的数显然是 (长度为 11 的) 一段连续的自然数。
- {1}{3,2}{4}{1}{3,2}{4}:{3,2}{3,2} 包含 22 到 33,是一段连续的自然数,另外 {1}{1} 和 {4}{4} 显然也是。
- {1}{3,2,4}{1}{3,2,4}:{3,2,4}{3,2,4} 包含 22 到 44,是一段连续的自然数,另外 {1}{1} 显然也是。
- {1,3,2}{4}{1,3,2}{4}:{1,3,2}{1,3,2} 包含 11 到 33,是一段连续的自然数,另外 {4}{4} 显然也是。
- {1,3,2,4}{1,3,2,4}:只有一个子数组,包含 11 到 44,是一段连续的自然数
题解: AcWing 4668. 数组切分 - AcWing
//4 3 2 1
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 10010, mod = 1000000007;
int n;
int a[N];
int f[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
f[0]=1;
for(int i=1;i<=n;i++)
{
int Max=a[i],Min=a[i];
for(int j=i;j>=1;j--)
{
Min=min(Min,a[j]);
Max=max(Max,a[j]);
//连续自然数
if((i-j)==(Max-Min))
f[i]=(f[i]+f[j-1])%mod;//f[i]方案数由各种f[j-1]相加组成
}
}
cout<<f[n];
}
糖果
蓝桥杯.糖果(状压dp)_糖果 蓝桥杯-CSDN博客https://blog.csdn.net/qq_59700927/article/details/123118678
糖果店的老板一共有 M� 种口味的糖果出售。
为了方便描述,我们将 M� 种口味编号 1∼M1∼�。
小明希望能品尝到所有口味的糖果。
遗憾的是老板并不单独出售糖果,而是 K� 颗一包整包出售。
幸好糖果包装上注明了其中 K� 颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。
给定 N� 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。
第一行包含三个整数 N,M,K�,�,�。
接下来 N� 行每行 K� 个整数 T1,T2,⋅⋅⋅,TK�1,�2,···,��,代表一包糖果的口味。
一个整数表示答案。
如果小明无法品尝所有口味,输出 −1−1。
#include <bits/stdc++.h>
using namespace std;
int n, m, k, x;
int a[110]; //袋子
int dp[1<<21];//糖果情况 二进制
int main(void)
{
cin >>n >>m >>k;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= k; j++){
//读入每包糖果
cin >>x;
a[i] |= 1 << (x - 1);
//x是几,位置:几-1 位置和进制关系 位置1,2的0进制 多1的关系
}
}
//dp初始化
//因为是不断刷新最小值,所以初始化应该是最大值
memset(dp, 127, sizeof(dp));
dp[0] = 0;
for(int i = 1; i <= n; i++)//枚举袋子找min
{
for(int j = 0; j <= (1 << m)-1 ; j++)//dp[i]有i种糖果需要的袋数 j:某串二进制
//(1<<m) -1 :111111
{
//如果最小次数已经大于n,跳过
if(dp[j] > n) continue;
//将原来得到糖果所需的次数与当前次数比较并赋值
dp[j | a[i]] = min(dp[j | a[i]], dp[j] + 1);//a[i]:每包情况 j|a[i]:原来已经的二进制|刚拿的一个对应袋子里糖果数二进制==现在
}
}
//如果拿到所有糖果((1<<m)-1)所需次数大于n(其实应该是因为某种糖果没有),输出-1
if(dp[(1<<m)-1] > n) cout <<-1;//1<<m -1 :111111
else cout <<dp[(1<<m)-1];
return 0;
}
李白打酒
AcWing 4408. 2022第十三届蓝桥杯 C++/C B组 李白打酒加强版 (超级详细版) - AcWing
f[n-1][m][1]:最后一次正好喝完酒 喝1斤,所以前一次正好剩下1斤
#include<bits/stdc++.h>
using namespace std;
const int N = 110, M = 1000000007;
int n, m, f[N][N][N];
int main()
{
scanf("%d%d", &m, &n);
for(int i = 0; i <= n; i ++ )
{
for(int j = 0; j <= m; j ++ )
{
for(int k = 0; k < N; k ++ )
{
if(i == 0 && j == 0 && k == 2)
f[i][j][k] = 1;
if(i == 0 && j == 0)
continue;
if(i > 0)
f[i][j][k] = (f[i][j][k] + f[i - 1][j][k + 1]) % M;
if(j > 0 && k % 2 == 0)
f[i][j][k] = (f[i][j][k] + f[i][j - 1][k / 2]) % M;
}
}
}
printf("%d", f[n - 1][m][1]);
return 0;
}