7.1简单枚举
除数,set去重
例题1
枚举,但是只用枚举后面,前面就可以推出来,再去重(用set)
c++11
#include
using namespace std;
int N, num=0;
int main() {
while (scanf("%d", &N) == 1 && N != 0) {
if (num != 0) puts(""); num++; // 连续的测试用例间需有空行
char buf[100]; string s; int cnt=0;
for (int fj=1234; ; fj++) { // 枚举1234 - 98765
sprintf(buf, "%05d%05d", fj*N, fj); // 分子,分母转为字符串
s = buf;
if (s.size() > 10) break; // 其中一个超过5位数
unordered_set<char> _set(s.begin(), s.end()); // 判重
if (_set.size() == 10) { // 刚好整除且无重复数字
printf("%s / %s = %d\n", s.substr(0,5).c_str(), s.substr(5).c_str(), N);
cnt ++;
}
}
if (cnt == 0) printf("There are no solutions for %d.\n", N);
}
return 0;
}
例题2
最大乘积,暴力枚举(起点和终点)
枚举起点,终点,再从起点乘到终点,是三重循环
但是可以优化到二重循环,终点每往后移动就乘以前面已经算好的,看是新得出的结果(pdt)大还是之前算的结果(maxp)大。
#include
using namespace std;
int main() {
int n, a[20], num=0;
while (scanf("%d", &n) == 1) {
for (int i=0; i < n; i ++) scanf("%d", &a[i]);
long long maxp=0, pdt=1;
for (int i=0; i < n; i ++) { // 连续子序列起点
pdt=1;
for (int j=i; j < n; j ++) { // 终点(在j~n过程中,每个j表示一个终点)
pdt *= (long long)a[j];
if (maxp <= pdt) maxp = pdt; // 取较大值
}
}
printf("Case #%d: The maximum product is %lld.\n\n", ++num, maxp);
}
return 0;
}
例题3
暴力枚举,找隐藏条件
隐藏条件:y<=2k。所以枚举y,再判断x即可
并且y是从k+1开始的。
看有没有整数x=ky/(y-k)怎么判断x是不是个整数,就是ky%(y-k)==0(※※※)
#include
#include
#include
int mx[5001],my[5001];
int main(){
int k;
//freopen("n.out","w",stdout);
while(scanf("%d",&k)==1&&k){
int cnt=0;//隐含y<=2k
for(int y=1+k;y<=2*k;++y)if((k*y)%(y-k)==0){mx[cnt]=(k*y)/(y-k);my[cnt++]=y;}
printf("%d\n",cnt);
for(int i=0;i<cnt;++i)printf("1/%d = 1/%d + 1/%d\n",k,mx[i],my[i]);
}
return 0;
}
7.2枚举排列
例题1
素数环,回溯剪枝
如果用next_permutation全排列,超时,像这种要用回溯剪枝
void dfs(int cur){
if(cur==n&&isp[a[0]+a[n-1]]){
for(int i=0;i<n;i++) printf("%d",a[i]);
printf("\n");
}
else for(int i=2;i<=n;i++)//尝试放置每一个数
if(!vis[i]&&isp[i+a[cur-1]]){
A[cur]=i;
vis[i]=1;
dfs(cur+1);
vis[i]=0;
}
}
例题2
字符串判断+dfs
这题输出很奇葩
#include
using namespace std;
string str;int k,l;
int flag=0;
int cnt=0;
int judge()
{
int s=str.size();
for(int i=1;i<=(s/2);i++)
{
if(str.substr(s-i,i)==str.substr(s-i-i,i)) return 0;//有相邻重复字串
}
return 1;
}
void dfs()
{
for(int i=0;i<l;i++)
{
if(flag==1) return;
str+=char('A'+i);
if(judge())
{
cnt++;//cnt代表困难串
if(cnt==k)
{
flag=1;
for (int j=0; j < str.size(); j ++)
{
cout<<str[j];
if ((j+1)%64 == 0 || j+1 == str.size()) puts("");
else if ((j+1)%4 == 0) putchar(' ');
}
cout<<str.size()<<endl;
return ;
}
dfs();
if(flag==1) return;
}
str.erase(str.size()-1);
}
}
int main()
{
while(cin>>k>>l)
{
if(k==0&&l==0) break;
str.clear();
flag=0;
cnt=0;
dfs();
}
}
例题3
倒水问题 bfs+dijkstra
用dijkstra算法,优先权队列保存
确定前两个杯子就可以确定最后一个杯子,所以只要开二维数组
更新两个:
①每取出一个节点的时候,判断这个状态每个杯子水量min,然后更新
②更新结点,也就是普通dijsktra的结点路径的更新
//摘自洛谷题解
#include
#include
#include
using namespace std;
const int MAXN=200+1;
int vis[MAXN][MAXN],dis[MAXN][MAXN];
int jug[3],ans[MAXN];
struct Node
{
int v[3],dist;
Node(int a,int b,int c,int d)
{
v[0]=a;v[1]=b;v[2]=c;
dist=d;
}
bool operator < (const Node& rhs)
const{
return dist > rhs.dist;//倒水量大的,优先权更小
}
};
void update_ans(Node u)
{
for(int i=0;i<3;i++)
{
int amount=u.v[i];//保存3个杯子的状态,看有没有到d或者小于d的
if(ans[amount] < 0 || ans[amount] > u.dist)//更新,放入更小到水量的那个,或者之前没有到达过这个状态
ans[amount] = u.dist;
}
}
void Dijkstra(int a,int b,int c,int d)
{
jug[0]=a;jug[1]=b;jug[2]=c;//最大容量
memset(ans,-1,sizeof(ans));
memset(vis,0,sizeof(vis));
memset(dis,0x7f,sizeof(dis));
priority_queue<Node>Q;
Q.push(Node(0,0,c,0));//初始状态
dis[0][0]=0;
while(!Q.empty())
{
Node u=Q.top();Q.pop();
if(vis[u.v[0]][u.v[1]])continue;
vis[u.v[0]][u.v[1]] = 1;//标记,这个节点已经取出过
update_ans(u);//更新到水量
//倒水,可以从i到j
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(i != j)
{
int amount=min(u.v[i],jug[j]-u.v[j]);//将 i 中的水倒入 j 中;
Node x=u;
x.v[i] -= amount;x.v[j] += amount;
x.dist = u.dist + amount;
if(!vis[x.v[0]][x.v[1]] && dis[x.v[0]][x.v[1]] > x.dist)//更新最短路径状态
{
dis[x.v[0]][x.v[1]] = x.dist;
Q.push(x);
}
}
}
}
void print_ans(int aim)
{
for(int d=aim;d>=0;d--)//如果可以d升输出,不然就输出离d最近的
if(ans[d] >=0 )
{
cout << ans[d] <<" "<< d <<endl;
break;
}
}
int main()
{
int T;
cin >> T;
while(T--)
{
int a,b,c,d;
cin >>a>>b>>c>>d;
Dijkstra(a,b,c,d);
print_ans(d);
}
return 0;
}
例题4
图+双向bfs
题解来自这里
#include
#include
#include
#include
using namespace std;
int w, h, n, s[3], t[3];
char dataset[20][20];
int G[200][5], color[200][200][200], dist[200][200][200], redist[200][200][200];
int deg[200]; //记录每个编号为i的空格周围可以走的步数
int dx[] = {0, -1, 1, 0, 0};
int dy[] = {0, 0, 0, -1, 1};
inline int ID(int a, int b, int c) { //对状态进行编码
return (a << 16) | (b << 8) | c;
}
inline bool conflict(int a, int b, int a2, int b2) {
return ((a2 == b2) || (a == b2 && b == a2));//不能在同一个位置,也不能交叉换
}
int bfs() {
queue<int> qf; //记录正向bfs
queue<int> qb; //记录反向bfs
dist[s[0]][s[1]][s[2]] = 0;
dist[t[0]][t[1]][t[2]] = 1; //分别记录正反两种遍历走了多少步数
qf.push(ID(s[0], s[1], s[2]));
qb.push(ID(t[0], t[1], t[2])); //起点终点分别压入队列
color[s[0]][s[1]][s[2]] = 1;
color[t[0]][t[1]][t[2]] = 2; //分别标注正反两种遍历已经走过的
while(!qf.empty() || !qb.empty()) {
int fnum = qf.size(), bnum = qb.size();
while(fnum--) {//先算正的,要一整层算,所以while(fnum--)
int u = qf.front(); qf.pop();
int a = (u >> 16) & 0xff, b = (u >> 8) & 0xff, c = u & 0xff;
//三层循环,a,b,c分别走路,再判断有没有冲突
for(int i = 0; i < deg[a]; i++) {//遍历图
int a2 = G[a][i];//a移动
for(int j = 0; j < deg[b]; j++) {//b移动
int b2 = G[b][j];
//a和b冲突,剪枝
if(conflict(a, b, a2, b2)) continue;
for(int k = 0; k < deg[c]; k++) {//c移动
int c2 = G[c][k];
if(conflict(a, c, a2, c2) || conflict(b, c, b2, c2)) continue;
//这个位置正反向都没到过,正向遍历先到了
if(color[a2][b2][c2] == 0) {
dist[a2][b2][c2] = dist[a][b][c] + 1;
color[a2][b2][c2] = 1;
qf.push(ID(a2, b2, c2));
}
//这个地方反向已经到过了,说明距离就是正反向相加
else if(color[a2][b2][c2] == 2) {
return dist[a][b][c] + dist[a2][b2][c2];
}
}
}
}
}
//反向,和正向一样的
while(bnum--) {
int u = qb.front(); qb.pop();
int a = (u >> 16) & 0xff, b = (u >> 8) & 0xff, c = u & 0xff;
for(int i = 0; i < deg[a]; i++) {
int a2 = G[a][i];
for(int j = 0; j < deg[b]; j++) {
int b2 = G[b][j];
if(conflict(a, b, a2, b2)) continue;
for(int k = 0; k < deg[c]; k++) {
int c2 = G[c][k];
if(conflict(a, c, a2, c2) || conflict(b, c, b2, c2)) continue;
if(color[a2][b2][c2] == 0) {
dist[a2][b2][c2] = dist[a][b][c] + 1;
color[a2][b2][c2] = 2;
qb.push(ID(a2, b2, c2));
}
else if(color[a2][b2][c2] == 1) {
return dist[a][b][c] + dist[a2][b2][c2];
}
}
}
}
}
}
return -1;
}
int main() {
freopen("input.txt", "r", stdin);
while(~scanf("%d%d%d\n", &w, &h, &n) && n) {
for(int i = 0; i < h; i++) fgets(dataset[i], 20, stdin); //此处不能用scanf来处理输入,会TLE
int cnt = 0, x[200], y[200], id[20][20]; //从图中抽取出空间并求出初始状态和目标状态
for(int i = 0; i < h; i++)
for(int j = 0; j < w; j++) {
if(dataset[i][j] != '#') {
x[cnt] = i; y[cnt] = j; id[i][j] = cnt;
if(islower(dataset[i][j])) s[dataset[i][j] - 'a'] = cnt; //初始状态
else if(isupper(dataset[i][j])) t[dataset[i][j] - 'A'] = cnt; //目标状态
cnt++; //注意这里的cnt++不能偷懒在上面一行末尾,因为这样有时候cnt++会没有执行
}
}
//邻接矩阵转化成邻接表
for(int i = 0; i < cnt; i++) { //利用空格建立图
deg[i] = 0;
for(int j = 0; j < 5; j++) {
int nx = x[i] + dx[j]; int ny = y[i] + dy[j];
if(dataset[nx][ny] != '#') G[i][deg[i]++] = id[nx][ny];
}
}
if(n <= 2) { deg[cnt] = 1; G[cnt][0] = cnt; s[2] = t[2] = cnt++; }
if(n <= 1) { deg[cnt] = 1; G[cnt][0] = cnt; s[1] = t[1] = cnt++; }
memset(dist, 0, sizeof(dist));
memset(color, 0, sizeof(color));
if(s[0] == t[0] && s[1] == t[1] && s[2] == t[2]) printf("0\n");//判断起点和终点是不是在同一位置
else printf("%d\n", bfs());
}
return 0;
}
例题5
首先说说IDS,就DFS限定一个层数上限maxd,如果在maxd范围内没有找到解,就增加maxd,继续搜索。
当访问到当前结点u时,估计还要搜索h(u)层,如果h(u)+当前层数d>maxd的时候就剪枝,这就是IDA*。
(IDA*)属于DFS,当状态空间某一层的结点数无穷大时,BFS失效,只能DFS。
相比BFS,它的空间占用少(不需要判重复),时间效率也不低。
注意:A*的关键在于选取估价函数h()。
/*
solution:
采用IDA*算法
note:
IDA*算法就是从小到大枚举深度上限,每次执行只考虑最大深度之内的结点,再设计一个乐观估价函数,用来剪枝。
其实就是 “迭代加深搜索” + "乐观函数剪枝"
*/
#include
#include
#include
using namespace std;
const int maxn = 10;
int n, book[maxn];
bool is_goal() {//检验是否1~n
bool ok = true;
for(int i = 0; i < n; i++) {
if(book[i] != i + 1) {
ok = false;
break;
}
}
return ok;
}
int h() {//估计代价函数,还要剪切几次
int cnt = 0;
for(int i = 0; i < n - 1; i++) {
if(book[i] + 1 != book[i + 1])
cnt++;
}
if(book[n - 1] != n) cnt++;
return cnt;
}
bool dfs(int d, int maxd) {
if(3 * d + h() > maxd * 3) return false; //剪枝
if(is_goal()) return true;
//扩展节点同时进行加深搜索
int old_book[maxn]; //因为扩展节点后book会随之改变,为了保存原来的book
int past_to[maxn]; //保存要剪切后的部分
for(int i = 0; i < n; i++)
for(int j = i; j < n; j++) { //依次枚举要剪切的部分,i是左端开头,j是右端结尾
memcpy(old_book, book, sizeof(book)); //保存原来book
int cnt = 0;
for(int k = 0; k < n; k++)
if(k < i || k > j)
past_to[cnt++] = book[k]; //剪切后的部分
for(int k = 0; k <= cnt; k++) { //依次枚举要插入第k位置前面
int cnt2 = 0; //执行粘贴操作
for(int p = 0; p < k; p++) book[cnt2++] = past_to[p];
for(int p = i; p <= j; p++) book[cnt2++] = old_book[p];
for(int p = k; p < cnt; p++) book[cnt2++] = past_to[p];
if(dfs(d + 1, maxd)) return true; //加深搜索
memcpy(book, old_book, sizeof(old_book)); //如果加深搜索失败的话就返回原来的数组状态
}
}
return false;
}
int solve() {
if(is_goal()) return 0;
for(int maxd = 0; maxd < 9; maxd++) //枚举层数,最多不超过9层dfs即可求出答案
if(dfs(0, maxd)) return maxd;
return -1;
}
int main()
{
//freopen("input.txt", "r", stdin);
int kase = 0;
while(~scanf("%d", &n) && n) {
memset(book, 0, sizeof(book));
for(int i = 0; i < n; i++) scanf("%d", &book[i]);
printf("Case %d: %d\n", ++kase, solve());
}
return 0;
}
总结:
①直接枚举
②枚举子集和排列
③回溯法(搜索对象的选取、最优剪枝、减少无用功)
④状态空间搜索(双向广度优先搜索、A*)
⑤迭代加深搜索(IDS),若对迭代加深搜索加上一个预测函数,就会变成IDA*
1、构造法:
uva120
#include
#include
#include
#include
#include
using namespace std;
int n;
int a[10005];
string s;
void solve(int p)
{
for(int i=0;i<p-i;i++)
{
swap(a[i],a[p-i]);
}
cout<<n-p<<" ";
}
int main()
{
while(getline(cin,s))
{
cout<<s<<endl;
stringstream ss(s);
n=0;
while(ss>>a[n])
n++;
for(int i=n-1;i>0;i--)
{
int p=max_element(a,a+i+1)-a;//查找最大元素的位置
if(p==i)//如果=i则说明它在自己的位置上
continue;
if(p>0)//否则将最大元素翻转到顶部
solve(p);
solve(i);//翻转到符合条件的位置
}
cout<<"0"<<endl;
}
}
2、uva1605
构造:一共只有两层,每层都是n*n,第一层第i行全都是国家i,第二层j列全都是国家j
3、uva1152 给定4个n元素的集合ABCD,各从集合取abcd使得a+b+c+d=0问有几种取法
(1)unordered_map写法(7090ms)
unordered_map就是哈希表,复杂度o(n),比map快很多
#include
#include
using namespace std;
int a[4005];
int b[4005];
int c[4005];
int d[4005];
int main()
{
int t;
cin>>t;
for(int k=0;k<t;k++)
{
if(k!=0) cout<<endl;
unordered_map<int,int> m;
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
}
int temp;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
temp=a[i]+b[j];
m[temp]++;
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
temp=-c[i]-d[j];
ans+=m[temp];
}
}
cout<<ans<<endl;
}
return 0;
}
(2)二分查找
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int a[5000], b[5000], c[6000], d[6000], p[17000000];
int main()
{
int t, n, m, ans, i, j, k;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
m=0;
for(i=0;i<n;i++)
{
scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
}
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
p[m++]=a[i]+b[j];
}
}
sort(p,p+m);
int low, high, mid;
ans=0;
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
int x=-c[i]-d[j];
low=0, high=m-1;
while(low<=high)
{
mid=low+high>>1;
if(p[mid]>x) high=mid-1;
else if(p[mid]<x) low=mid+1;
else
{
for(k=mid;k>=0;k--)
{
if(p[k]==x)
{
ans++;
}
else
break;
}
for(k=mid+1;k<m;k++)
{
if(p[k]==x)
{
ans++;
}
else
break;
}
break;
}
}
}
}
printf("%d\n",ans);
if(t)
puts("");
}
return 0;
}
2、问题分解
uva11134
首先行列可以分开考虑,它们互不影响,单单考虑行的话,其实就是给定若干个区间,然后给每个区间都分配一个点,使得点不重复的情况下都能落在相应区间中,可以对区间按右端点升序排序,右端点相等时按左端点升序排序,从左往右看每个区间,尽量往区间的左端点分配即可.
#include
using namespace std;
const int maxn=5050;
struct node{
int id,le,ri;
node(int i,int l,int r):id(i),le(l),ri(r){}
bool operator<(const node& e)const{
if(ri==e.ri) return le<e.le;
return ri<e.ri;
}
};
int n;
vector<node>x,y;
bool used[maxn];
int ansx[maxn],ansy[maxn];
int main(){
while(scanf("%d",&n)==1 && n){
x.clear();
y.clear();
for(int i=0;i<n;++i){
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x.push_back(node(i,x1,x2));
y.push_back(node(i,y1,y2));
}
bool ok=1;
sort(x.begin(),x.end());
memset(used,0,sizeof(used));
for(int i=0;i<x.size();++i){
bool flag=0;
for(int j=x[i].le;j<=x[i].ri;++j){
if(!used[j]){
flag=1;
used[j]=1;
ansx[x[i].id]=j;
break;
}
}
if(!flag){ ok=0;break; }
}
if(ok){
sort(y.begin(),y.end());
memset(used,0,sizeof(used));
for(int i=0;i<y.size();++i){
bool flag=0;
for(int j=y[i].le;j<=y[i].ri;++j){
if(!used[j]){
flag=1;
used[j]=1;
ansy[y[i].id]=j;
break;
}
}
if(!flag){ ok=0;break; }
}
}
if(!ok) puts("IMPOSSIBLE");
else for(int i=0;i<n;++i){ printf("%d %d\n",ansx[i],ansy[i]); }
}
return 0;
}
等价转换
uva11504
#include
using namespace std;
#define maxn 100050
int a[maxn];
int main()
{
int n;
while(cin>>n&&n)
{
long long sum=0;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<n;i++)
{
if(a[i]>0)
{
a[i+1]+=a[i];
sum+=abs(a[i]);
}
else//小于0,要从a[i+1]运过来
{
a[i+1]-=abs(a[i]);
sum+=abs(a[i]);
}
}cout<<sum<<endl;
}
return 0;
}
滑动窗口
uva11572
#include
#include
#include
using namespace std;
#define maxn 1000010
long long a[maxn];
int main()
{
int t;
cin>>t;
while(t--)
{
unordered_map<long long,int> m;
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int l=1;int r=1;
int ans=0;
//m保存数的下标,如果这个数还没出现过,就是0
while(r<=n)
{
while(r<=n&&m[a[r]]==0)
{
m[a[r]]=r;
r++;
}
ans=max(ans,(r-l));
while(l<=m[a[r]])
{
m[a[l]]=0;
l++;
}
}
cout<<ans<<endl;
}
}
例题1
这种题目,因为受到后续状态影响,所以用递推不清晰,用记忆化搜索比较好
//把一个立方体看成三种状态,比较好处理
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=90+5;
int n,V[maxn],d[maxn],G[maxn][maxn],a[3];//V数组保存每一种立方体的高
//d数组为以第几个立方体为起点时最大的高度为多少
//G数组为标记数组 第i个立方体是否可以作为第j个立方体的底
struct Cub
{
int x,y;
}cub[maxn];//保存每一种立方体的长和宽
void have_edge(int i,int j)//判断j能不能放到i上
{
if(cub[i].x>cub[j].x && cub[i].y>cub[j].y)
G[i][j]=1;
}
int dp(int i)
{
int &ans = d[i];//记忆化搜索,保存状态,以i为底最高能堆多少
if(ans > 0) return ans;
ans=V[i];//如果往上不能再堆,就是这个立方体的高
for(int j=0; j<3*n; j++)
if(G[i][j])//判断j能不能放到i上,一共有3*n个
ans=max(ans,V[i]+dp(j));//判断是用哪个搭在上面会叠更高
return ans;
}
int main()
{
int k=0;
while(scanf("%d",&n)!=EOF&&n)
{
int height=0;
memset(d,0,sizeof(d));
memset(G,0,sizeof(G));
for(int i=0; i<n; i++)
{
scanf("%d %d %d",&a[0],&a[1],&a[2]);
sort(a,a+3);//长>宽,这样只要三种状态就可以了
//存储,将一个立方体剖析成三种状态
cub[3*i].x=a[0],cub[3*i].y=a[1],V[3*i]=a[2];
cub[3*i+1].x=a[0],cub[3*i+1].y=a[2],V[3*i+1]=a[1];
cub[3*i+2].x=a[1],cub[3*i+2].y=a[2],V[3*i+2]=a[0];
}
for(int i=0; i<3*n; i++)
for(int j=0; j<3*n; j++)
have_edge(i,j);
//以每个状态为底做一次dp
for(int i=0; i<3*n; i++)
dp(i);
//看一种为底的高度最高
for(int i=0 ; i<3*n ; i++)
height=max(height,d[i]);
printf("Case %d: maximum height = %d\n",++k,height);
}
return 0;
}
例题2
那么假设i之前的景点都被走过,得到推导式:
dp[i+1][j]=dp[i][j]+Dist(i,i+1)
dp[i+1][i]=dp[i][j]+Dist(j,i+1)
从dp[i][j]出发,可能走到两种:
dp[i][j]–>dp[i+1][j] (i走到i+1)
dp[i][j]–>dp[i][i+1] (j走到i+1)–>dp[i+1][i] (由于i+1>i就交换了一下)
#include
#define INF 0x3f3f3f3f
#define eps 1e-6
typedef long long LL;
const double pi = acos(-1.0);
const long long mod = 1e9 + 7;
using namespace std;
int N;
struct data
{
double x,y;
}a[1005];
double dp[1005][1005];
double dist(int i,int j)//计算距离
{
return sqrt( (a[i].x - a[j].x) * (a[i].x - a[j].x) +
(a[i].y - a[j].y) * (a[i].y - a[j].y) );
}
//状态转移,i+1这个点要么是i过去的,要么是j过去的,保证了一定是从坐到右走一步
double fun(int i,int j)
{
if(dp[i][j] > 0)//记忆化搜索,已经知道这个状态还要走多远才到终点状态
return dp[i][j];
return dp[i][j] = min(fun(i + 1,j) + dist(i,i + 1),
fun(i + 1,i) + dist(j,i + 1));
}
int main()
{
int cas = 1;
while(cin >> N)
{
for(int i = 1;i <= N;i++)
cin >> a[i].x >> a[i].y;
memset(dp,0,sizeof(dp));
//已经走到N-1了,另一个点还在j,那么就是还剩下N-1到N的距离和j到N的距离
for(int j = 1;j < N - 1;j++)
dp[N - 1][j] = dist(N - 1,N) + dist(j,N);
double ans = fun(1,1);
printf("%.2f\n",ans);
}
return 0;
}
例题3
01背包问题
#include
#include
#include
using namespace std;
const int maxn=180*50+5;
int n,max_t;
int t[50+5]; //每首歌曲的时间
struct Node
{
int num; //总歌曲数
int time;//歌总时间
bool operator<(const Node &rhs)const//判断是否更优
{
return num<rhs.num || (num==rhs.num && time<rhs.time);
}
}dp[maxn];
int main()
{
int T;
scanf("%d",&T);
for(int kase=1;kase<=T;kase++)
{
printf("Case %d: ",kase);
scanf("%d%d",&n,&max_t);
memset(dp,0,sizeof(dp));
int sum=0;//所有歌曲总时长
for(int i=1;i<=n;i++)
{
scanf("%d",&t[i]);
sum +=t[i];
}
//max_t是我们最大需要考虑的时间
max_t = min(sum,max_t-1);
//注意max_t==sum和max_t==0时的情况.
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=max_t;j>=t[i];j--)
{
Node tmp;//tmp表示当选择第i首歌时的情况
tmp.num = dp[j-t[i]].num+1;
tmp.time = dp[j-t[i]].time+t[i];
if(dp[j]<tmp)//tmp更优
{
dp[j]=tmp;
}
}
}
printf("%d %d\n",dp[max_t].num+1,dp[max_t].time+678);
}
return 0;
}
例题4
f[i]=min{d[j]+1|s[j+1~i]}是回文串
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long int64;
const int INF = 0x3f3f3f3f;
const int MAXN = 1010;
char str[MAXN];
int f[MAXN];
bool isPalind(int l, int r){
while(l<r){
if(str[l] != str[r]) return false;
++l; --r;
}
return true;
}
int main(){
int T;
scanf("%d", &T);
while(T--){
scanf("%s", str+1);
int len = strlen(str+1);
memset(f, 0, sizeof(f));
for(int i=1; i<=len; ++i){
f[i] = i+1;
for(int j=1; j<=i; ++j)if(isPalind(j, i)){
f[i] = min(f[i], f[j-1]+1);
}
}
printf("%d\n", f[len]);
}
return 0;
}
例题5
简单DP
#include
#include
#include
#define INF 0x3f3f3f3f
#define LL long long
#define N 5005
using namespace std;
unsigned char c;
int dp[N][N], cnt[N][N];
int bf[100], bs[100], ef[100], es[100];
char f[N], s[N];
int main()
{
int t;
scanf("%d", &t);
while (t--){
scanf("%s %s", f+1, s+1);
int lenf = strlen(f+1), lens = strlen(s+1);
for (c = 'A'; c <= 'Z'; c++) bf[c] = bs[c] = INF, ef[c] = es[c] = 0;
//bf找到字符最开始的位置,ef找到字符最后的位置
for (int i = 1; i <= lenf; i++){
c = f[i];
bf[c] = min(i, bf[c]);
ef[c] = i;
}
//bs,es同上
for (int i = 1; i <= lens; i++){
c = s[i];
bs[c] = min(i, bs[c]);
es[c] = i;
}
//cnt[i][j]为分别取走i个颜色和j个颜色时还有多少种颜色已经出现但尚未结束
for (int i = 0; i <= lenf; i++){
for (int j = 0; j <= lens; j++){
//从第一个字符串下来,
if (i){
cnt[i][j] = cnt[i-1][j];
c = f[i];
if (bf[c] == i && bs[c] > j) cnt[i][j]++;//有新增的元素
if (ef[c] == i && es[c] <= j) cnt[i][j]--;//已经到最后一个了,不会再影响到这个元素
}
//同上
if (j){
cnt[i][j] = cnt[i][j-1];
c = s[j];
if (bs[c] == j && bf[c] > i) cnt[i][j]++;
if (es[c] == j && ef[c] <= i) cnt[i][j]--;
}
}
}
//dp[i][j]表示
for (int i = 0; i <= lenf; i++)
for (int j = 0; j <= lens; j++){
if (!i && !j) continue;
dp[i][j] = INF;
if (i) dp[i][j] = min(dp[i][j], dp[i-1][j]+cnt[i-1][j]);
if (j) dp[i][j] = min(dp[i][j], dp[i][j-1]+cnt[i][j-1]);
}
printf("%d\n", dp[lenf][lens]);
}
return 0;
}
例题六
区间dp
写法一:记忆化搜索
#include
#include
using namespace std;
#define ms(s) memset(s,0,sizeof(s))
const int maxn = 50 + 5;
int n, L, a[maxn], vis[maxn][maxn], d[maxn][maxn];
int dp(int i, int j)
{
if(i >= j-1)
return 0;
if(vis[i][j])//standard memorize
return d[i][j];
vis[i][j] = 1;
int& ans = d[i][j];//it is convenient to understand
ans = -1;
for(int k = i+1; k <= j-1; k++)
{
int v = dp(i,k) +dp(k,j) + a[j]-a[i];
if(ans < 0 || v < ans)
ans = v;
}
return ans;
}
int main()
{
while(scanf("%d%d",&L, &n) == 2 && L)
{
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
a[0] = 0;
a[n + 1] = L;
ms(vis);
printf("The minimum cutting is %d.\n",dp(0, n+1));
}
return 0;
}
递推写法:
/*
* Author: Bingo
* Created Time: 2015/2/13 18:33:03
* File Name: uva10003.cpp
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxint = -1u>>1;
int l,n;
int a[1050],d[50];
int f[55][55];
int main()
{
while (cin>>l&&l){
cin>>n;
d[0]=0;
for (int i=1;i<=n;i++) {
cin>>d[i];
}
d[n+1]=l;
n=n+1;
memset(f,0,sizeof(f));
int len;
for (int i=0;i<=n-2;i++)
f[i][i+2]=d[i+2]-d[i];
for (len=2;len<=n;len++)
for (int i=0;i<=n-len;i++){
int &t=f[i][i+len];
t=100000;
for (int k=i+1;k<i+len;k++) {
t=min(t,f[i][k]+f[k][i+len]+d[i+len]-d[i]);
//printf("%d %d %d\n",d[i],d[i+len],t);
}
}
cout<<"The minimum cutting is "<<f[0][n]<<"."<<endl;
}
return 0;
}
例题7
括号匹配,打印
#include
#include
using namespace std;
#include
#include
int n;
char s[105];
int d[105][105];
int match(char a,char b)
{
if((a=='('&&b==')')||(a=='['&&b==']'))
return 1;
return 0;
}
void dp()
{
for(int i=0;i<n;i++)
{
d[i+1][i]=0;
d[i][i]=1;
}
for(int i=n-2;i>=0;i--)
{
for(int j=i+1;j<n;j++)
{
d[i][j]=n;
if(match(s[i],s[j])) d[i][j]=min(d[i][j],d[i+1][j-1]);
for(int k=i;k<j;k++)
d[i][j]=min(d[i][k]+d[k+1][j],d[i][j]);
}
}
}
void print(int i,int j)
{
if(i>j) return;
if(i==j)
{
if(s[i]=='('||s[i]==')')
printf("()");
else
printf("[]");
return;
}
if(match(s[i],s[j])&&d[i][j]==d[i+1][j-1])
{
putchar(s[i]);
print(i+1,j-1);
putchar(s[j]);
return;
}
for(int k=i;k<j;k++)
{
if(d[i][j]==d[i][k]+d[k+1][j])
{
print(i,k);
print(k+1,j);
return ;
}
}
}
int main()
{
int t;
scanf("%d",&t);
getchar();
while(t--)
{
memset(d,0,sizeof(d));
fgets(s,105,stdin);
fgets(s,105,stdin);
n=strlen(s)-1;
//cout<
dp();
print(0,n-1);
cout<<endl;
if(t)
cout<<endl;
}
return 0;
}
树形DP
模板:树的最大独立集
典例
#include
#include
#include
using namespace std;
int n;
int Dp[6005][2];//状态
vector<int>G[6005];//儿子
void Tdp(int x,int fa){
Dp[x][1]=1;//因为每个点都算一个,即使是叶节点
for(int i=0;i<G[x].size();i++){//枚举儿子
int v=G[x][i];//简化程序
if(v==fa)continue;//儿子等于父亲
Tdp(v,x);//递归求解,先计算下层的节点(想想为什么)
Dp[x][1]+=Dp[v][0];
Dp[x][0]+=max(Dp[v][0],Dp[v][1]);//转移
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);//存入数组
}
Tdp(1,0);//把1号当成根,由于题目中没有0号节点,所以父亲为0(最好为-1)
printf("%d\n",max(Dp[1][0],Dp[1][1]));//最优解
}
例题1
// UVa1220 Party at Hali-Bula
// Rujia Liu
//
// rev 2: fixed bug reported by EndlessCheng
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 200 + 5;
int cnt;
vector<int> sons[maxn];
int n, d[maxn][2], f[maxn][2];
map<string, int> dict;
int ID(const string& s) {
if(!dict.count(s)) dict[s] = cnt++;
return dict[s];
}
int dp(int u, int k) {
f[u][k] = 1;//只有一种方案
d[u][k] = k;//k=1,叫,k=0,不叫。如果是叶子结点返回0or1
for(int i = 0; i < sons[u].size(); i++) {
int v = sons[u][i];
if(k == 1) {
d[u][1] += dp(v, 0);//交了老板,下属一定不叫,记住是+=
if(!f[v][0]) f[u][1] = 0;
} else {//不叫老板,可以叫直系下属,也可以不叫直系下属
d[u][0] += max(dp(v, 0), dp(v, 1));
if(d[v][0] == d[v][1]) f[u][k] = 0;
else if(d[v][0] > d[v][1] && !f[v][0]) f[u][k] = 0;
else if(d[v][1] > d[v][0] && !f[v][1]) f[u][k] = 0;
}
}
return d[u][k];
}
int main() {
string s, s2;
while(cin >> n >> s) {
cnt = 0;
dict.clear();
for(int i = 0; i < n; i++) sons[i].clear();
ID(s);//给节点标号,先给老板标号
for(int i = 0; i < n-1; i++) {
cin >> s >> s2;
sons[ID(s2)].push_back(ID(s));
}
printf("%d ", max(dp(0, 0), dp(0, 1)));//请老板,不请老板
bool unique = false;
if(d[0][0] > d[0][1] && f[0][0]) unique = true;
if(d[0][1] > d[0][0] && f[0][1]) unique = true;
if(unique) printf("Yes\n"); else printf("No\n");
}
return 0;
}
例题1
快速幂+模算术
#include
#include
#include
#include
#include
typedef unsigned long long ll;
using namespace std;
const int maxn=1000+10;
ll a,b;
int f[maxn*maxn],n,M;
int pow(ll a,ll p,int Mod)//快速幂
{
int ret=1;
while(p)
{
if(p & 1)ret*=a,ret%=Mod;//如果p是奇数
a*=a;a%=Mod;
p>>=1;
}
return ret;
}
inline void solve()
{
cin>>a>>b>>n;
if(n==1||!a){printf("0\n");return ;}
f[1]=1,f[2]=1;
for(int i=3;i<=n*n+10;i++)
{
f[i]=f[i-1]+f[i-2];f[i]%=n;
if(f[i]==f[2]&&f[i-1]==f[1]) {M=i-2;break;}
//如果后面两个数和前面两个数相同,说明出现了循环节。循环节长度为M
}
int k=pow(a%M,b,M);
printf("%d\n",f[k]);
}
int main()
{
int T;cin>>T;
while(T--) solve();
return 0;
}
例题2
唯一分解定理
※※唯一分解定理以前介绍过,大意就是每个数都可以唯一地分解为一堆质数的幂的乘积,如90 = 2 * 3² * 5.其中2和5是一次幂,3是二次幂。
※※这样我们回到题目数据,显然很多时候我们把分子分母都分解以后会发现能约掉很多质数的幂,这样的话可以采取这样一种策略:用一个数组记录每个质数的幂指数,如果是分子的话就加,是分母的话就减,免去了反复的无用乘除法。当把分子分母们都处理完以后,约分后的分子分母就是答案的分数形式了,此时再求出结果即可。
题解
例题1
表达式树
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int mx = 60000;
int T, rnd, cnt;
char expr[mx * 5], *p;
int done[mx];
struct Node
{
string s;
int ha, left, right;//把string转为数字(27进制)
bool operator < (const Node&b)const//由于下面要用到map::count函数,所以必须要重载小于号
{
if (ha != b.ha)return ha < b.ha;
if (left != b.left)return left < b.left;//哈希相同则比较出现的顺序
return right < b.right;
}
}node[mx];
map <Node, int>dict;//记录子树的编号
int solve()
{
int id = cnt++;//从0开始编号
Node&u = node[id];
u.left = u.right = -1;
u.s = "";
u.ha = 0;
while (isalpha(*p))
{
u.ha = u.ha * 27 + *p - 'a' + 1;//可以理解为27进制,a到z的编号为1到26
u.s.push_back(*p);//将该子树的字母放入s
p++;//向后扫描,遇到括号停止
}
if (*p == '(')//(L,R),递归处理左右子树
{
p++;//先跳过'('
u.left = solve(), p++;//返回左子树编号,并跳过','
u.right = solve(),p++;//返回右子树编号,并跳过')'
}
if (dict.count(u))
{
cnt--;//子树出现过,个数减少1
return dict[u];//返回这颗子树的编号
}
return dict[u] = id;//如果这棵树是首次出现,给它编号
}
void print(int v)
{
if (done[v] == rnd)printf("%d", v + 1);//已经输出过了,输出序号即可
else
{
done[v] = rnd;//不需要对done数组初始化,只需要用这一轮特有的rnd标记即可
printf("%s", node[v].s.c_str());//输出树根的字母
if (node[v].left != -1)//含有左右子树
{
putchar('(');
print(node[v].left);//递归输出左右子树
putchar(',');
print(node[v].right);
putchar(')');
}
}
}
int main()
{
freopen("test.txt", "r", stdin);
scanf("%d", &T);
for (rnd = 1; rnd <= T;rnd++)
{
dict.clear();
cnt = 0;
scanf("%s", expr);
p = expr;//用指针p扫描expr
print(solve());
putchar(10);//打印换行符
}
return 0;
}
2、最小生成树
kruskal、prim
3、求最短路dijkstra、bellman-ford、floyd算法
4、网络流