更新时间:2019 / 10 / 15
头文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
你就是个瓜皮
long long *a=new long long[T]; //申请虚拟数组空间
m = unique(b, b+n) - b; //去重函数、返回尾地址
memset(a,0x3f,sizeof(a));
----------------------------------------------------------
strcpy与strcat与strcmp:
substr有2种用法:
假设:string s = "0123456789";
string sub1 = s.substr(5); //只有一个数字5表示从下标为5开始一直到结尾:sub1 = "56789"
string sub2 = s.substr(5, 3); //从下标为5开始截取长度为3位:sub2 = "567"
char str[1024];
scanf("%[^\n]",&str);
getchar();
整行读入
方法一:scanf()读入char[]
使用方法:
char str[1024];
scanf("%[^\n]",&str);
getchar();
说明:在scanf函数中,可以使用%c来读取一个字符,使用%s读取一个字符串, 但是读取字符串时不忽略空格,读字符串时忽略开始的空格,并且读到空格为止,因此只能读取一个单词,而不是整行字符串。
其实scanf函数也可完成这样的功能,而且还更强大。这里主要介绍一个参数,%[ ],这个参数的意义是读入一个字符集合。[ ]是个集合的标志,因此%[ ]特指读入此集合所限定的那些字符,比如%[A-Z]是输入大写字母,一旦遇到不在此集合的字符便停止。如果集合的第一个字符是"",这说明读取不在"“后面集合的字符,既遇到”^"后面集合的字符便停止。注意此时读入的字符串是可以含有空格的,而且会把开头的空格也读进来。
注意:如果要循环的多次从屏幕上读取一行的话,就要在读取一行后,在用%c读取一个字符,将输入缓冲区中的换行符给读出来。否则的话,在下一次读取一行的时候,第一个就遇到’\n’,匹配不成功就直接返回了。这里可以用scanf()或者getchar()函数读取换行符。
方法二:getchar()读入char[]
使用方法:
char str[1024]; int i=0;
while((str[i]=getchar())!='\n')
i++;
getchar();
说明:这样一个一个读也可以,也会把开头的空格读进来。最后也需要考虑换行符,使用getchar()读出来。
方法三:gets()读入char[]
使用方法:
char str[1024];
gets(str);
说明:感觉这个就是多个getchar的集合函数,很好用。功能是从标准输入键盘上读入一个完整的行(从标准输入读,一直读到遇到换行符),把读到的内容存入括号中指定的字符数组里,并用空字符’\0’取代行尾的换行符’\n’。读入时不需要考虑换行符。
方法四:getline()读入string或char[]
使用方法:
string str;
getline(cin,str);//读入string
char str2[1024];
cin.getline(str2,1024);//读入char数组
说明:这是比较常用的方法,cin.getline第三个参数表示间隔符,默认为换行符’\n’。读入不需要考虑最后的换行符。
方法五:get()读入char[]
使用方法:
char str3[1024];
cin.get(str3,1024);//读入char数组
说明:get函数读入时需要考虑最后的换行符,也就是说,如果用get读入多行数据,要把’\n’另外读出来,一般使用cin.get(str,1024).get();来读入多组数据。
输入输出挂:
简易版:
inline int read() {
int x=0, f=1; char ch=getchar();
while(!isdigit(ch)) {if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
inline void print(int x) {
if(x<0) putchar('-'), x=-x;
if(x>9) print(x/10);
putchar(x%10+'0');
}
爆炸简易版:
namespace IO{
#define BUF_SIZE 100000
#define OUT_SIZE 100000
bool IOerror=0;
inline char nc(){
static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE;
if (p1==pend){
p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin);
if (pend==p1){IOerror=1;return -1;}
}
return *p1++;
}
inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';}
inline void read(int &x){
bool sign=0; char ch=nc(); x=0;
for (;blank(ch);ch=nc());
if (IOerror)return;
if (ch=='-')sign=1,ch=nc();
for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
if (sign)x=-x;
}
inline void read(char &c){
for (c=nc();blank(c);c=nc());
if (IOerror){c=-1;return;}
}
//fwrite->write
struct Ostream_fwrite{
char *buf,*p1,*pend;
Ostream_fwrite(){buf=new char[BUF_SIZE];p1=buf;pend=buf+BUF_SIZE;}
void out(char ch){
if (p1==pend){
fwrite(buf,1,BUF_SIZE,stdout);p1=buf;
}
*p1++=ch;
}
void print(int x){
static char s[15],*s1;s1=s;
if (!x)*s1++='0';if (x<0)out('-'),x=-x;
while(x)*s1++=x%10+'0',x/=10;
while(s1--!=s)out(*s1);
}
void flush(){if (p1!=buf){fwrite(buf,1,p1-buf,stdout);p1=buf;}}
~Ostream_fwrite(){flush();}
}Ostream;
inline void print(int x){Ostream.print(x);}
inline void print(char x){Ostream.out(x);}
inline void println(){Ostream.out('\n');}
inline void flush(){Ostream.flush();}
#undef OUT_SIZE
#undef BUF_SIZE
};
using namespace IO;
爆炸版:
namespace IO{
#define BUF_SIZE 100000
#define OUT_SIZE 100000
#define ll long long
bool IOerror=0;
inline char nc(){
static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE;
if (p1==pend){
p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin);
if (pend==p1){IOerror=1;return -1;}
}
return *p1++;
}
inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';}
inline void read(int &x){
bool sign=0; char ch=nc(); x=0;
for (;blank(ch);ch=nc());
if (IOerror)return;
if (ch=='-')sign=1,ch=nc();
for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
if (sign)x=-x;
}
inline void read(ll &x){
bool sign=0; char ch=nc(); x=0;
for (;blank(ch);ch=nc());
if (IOerror)return;
if (ch=='-')sign=1,ch=nc();
for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
if (sign)x=-x;
}
inline void read(double &x){
bool sign=0; char ch=nc(); x=0;
for (;blank(ch);ch=nc());
if (IOerror)return;
if (ch=='-')sign=1,ch=nc();
for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0';
if (ch=='.'){
double tmp=1; ch=nc();
for (;ch>='0'&&ch<='9';ch=nc())tmp/=10.0,x+=tmp*(ch-'0');
}
if (sign)x=-x;
}
inline void read(char *s){
char ch=nc();
for (;blank(ch);ch=nc());
if (IOerror)return;
for (;!blank(ch)&&!IOerror;ch=nc())*s++=ch;
*s=0;
}
inline void read(char &c){
for (c=nc();blank(c);c=nc());
if (IOerror){c=-1;return;}
}
//fwrite->write
struct Ostream_fwrite{
char *buf,*p1,*pend;
Ostream_fwrite(){buf=new char[BUF_SIZE];p1=buf;pend=buf+BUF_SIZE;}
void out(char ch){
if (p1==pend){
fwrite(buf,1,BUF_SIZE,stdout);p1=buf;
}
*p1++=ch;
}
void print(int x){
static char s[15],*s1;s1=s;
if (!x)*s1++='0';if (x<0)out('-'),x=-x;
while(x)*s1++=x%10+'0',x/=10;
while(s1--!=s)out(*s1);
}
void println(int x){
static char s[15],*s1;s1=s;
if (!x)*s1++='0';if (x<0)out('-'),x=-x;
while(x)*s1++=x%10+'0',x/=10;
while(s1--!=s)out(*s1); out('\n');
}
void print(ll x){
static char s[25],*s1;s1=s;
if (!x)*s1++='0';if (x<0)out('-'),x=-x;
while(x)*s1++=x%10+'0',x/=10;
while(s1--!=s)out(*s1);
}
void println(ll x){
static char s[25],*s1;s1=s;
if (!x)*s1++='0';if (x<0)out('-'),x=-x;
while(x)*s1++=x%10+'0',x/=10;
while(s1--!=s)out(*s1); out('\n');
}
void print(double x,int y){
static ll mul[]={1,10,100,1000,10000,100000,1000000,10000000,100000000,
1000000000,10000000000LL,100000000000LL,1000000000000LL,10000000000000LL,
100000000000000LL,1000000000000000LL,10000000000000000LL,100000000000000000LL};
if (x<-1e-12)out('-'),x=-x;x*=mul[y];
ll x1=(ll)floor(x); if (x-floor(x)>=0.5)++x1;
ll x2=x1/mul[y],x3=x1-x2*mul[y]; print(x2);
if (y>0){out('.'); for (size_t i=1;i
——部分数据结构——
queue
先进先出
定义 queue q; deque q;
访问队列中的元素个数 q.size() q.size()
判断队列空 q.empty() q.empty()
尾部压入新元素 q.push(x) q.push_back(x)
头部压入新元素 EOF q.push_front(x)
弹出队列的第一个元素 q.pop() q.pop_front()
弹出队列的最后一个元素 EOF q.pop_back()
访问队首元素 q.front() q.begin()
访问队尾元素 q.back() q.end()
删除所有元素 EOF q.clear()
删除任意位置x EOF q .erase(q.begin() + x)
set
begin() 返回set容器的第一个元素的地址
end() 返回set容器的最后一个元素地址
clear() 删除set容器中的所有的元素
empty() 判断set容器是否为空
max_size() 返回set容器可能包含的元素最大个数
size() 返回当前set容器中的元素个数
erase(it) 删除迭代器指针it处元素
map
-
map最基本的构造函数;
mapmapstring; mapmapint;
mapmapstring; map< char ,string>mapchar;
mapmapchar; mapmapint;
-
map添加数据;
map maplive;
maplive.insert(pair(102,"aclive"));
maplive.insert(map::value_type(321,"hai"));
maplive[112]="April";//map中最简单最常用的插入添加!
3,map中元素的查找:
find()函数返回一个迭代器指向键值为key的元素,如果没找到就返回指向map尾部的迭代器。
map::iterator l_it;;
l_it=maplive.find(112);
if(l_it==maplive.end())
cout<<"we do not find 112"<
4,map中元素的删除:
如果删除112;
map::iterator l_it;;
l_it=maplive.find(112);
if(l_it==maplive.end())
cout<<"we do not find 112"<
5,map中 swap的用法:
Map中的swap不是一个容器中的元素交换,而是两个容器交换;
For example:
#include
6.map的sort问题:
Map中的元素是自动按key升序排序,所以不能对map用sort函数:
For example:
#include
The original map m1 is:
1 20
2 50
3 60
4 40
6 40
7 30
7, map的基本操作函数:
C++ Maps是一种关联式容器,包含“关键字/值”对
begin() 返回指向map头部的迭代器
clear() 删除所有元素
count() 返回指定元素出现的次数
empty() 如果map为空则返回true
end() 返回指向map末尾的迭代器
equal_range() 返回特殊条目的迭代器对
erase() 删除一个元素
find() 查找一个元素
get_allocator() 返回map的配置器
insert() 插入元素
key_comp() 返回比较元素key的函数
lower_bound() 返回键值>=给定元素的第一个位置
max_size() 返回可以容纳的最大元素个数
rbegin() 返回一个指向map尾部的逆向迭代器
rend() 返回一个指向map头部的逆向迭代器
size() 返回map中元素的个数
swap() 交换两个map
upper_bound() 返回键值>给定元素的第一个位置
value_comp() 返回比较元素value的函数
vector
vector > points; //定义一个二维数组
points[0].size(); //指第一行的列数
1 、基本操作
(5)使用迭代器访问元素.
vector::iterator it;
for(it=vec.begin();it!=vec.end();it++)
cout<<*it<
(6)插入元素: vec.insert(vec.begin()+i,a);在第i+1个元素前面插入a;
(7)删除元素: vec.erase(vec.begin()+2);删除第3个元素
vec.erase(vec.begin()+i,vec.end()+j);删除区间[i,j-1];区间从0开始
(8)向量大小:vec.size();
(9)清空:vec.clear();
特别提示:这里有begin()与end()函数、front()与back()的差别
2、重要说明
vector的元素不仅仅可以是int,double,string,还可以是结构体,但是要注意:结构体要定义为全局的,否则会出错。
#include
using namespace std;
typedef struct rect {
int id;
int length;
int width;
//对于向量元素是结构体的,可在结构体内部定义比较函数,下面按照id,length,width升序排序。
bool operator< (const rect &a) const {
if(id!=a.id)
return id vec;
Rect rect;
rect.id=1;
rect.length=2;
rect.width=3;
vec.push_back(rect);
vector::iterator it=vec.begin();
cout<<(*it).id<<' '<<(*it).length<<' '<<(*it).width<
3、算法
需要注意的是:以方法一进行输出时,数组的下表必须保证是整数。
//打印vecClass,方法二:
for(int i=0;i::iterator it = vecClass.begin();it!=vecClass.end();it++) {
cout<<*it<<" ";
}
cout<
bitset
构造函数
bitset常用构造函数有四种,如下
bitset<4> bitset1; //无参构造,长度为4,默认每一位为0
bitset<8> bitset2(12); //长度为8,二进制保存,前面用0补充
string s = "100101";
bitset<10> bitset3(s); //长度为10,前面用0补充
char s2[] = "10101";
bitset<13> bitset4(s2); //长度为13,前面用0补充
cout << bitset1 << endl; //0000
cout << bitset2 << endl; //00001100
cout << bitset3 << endl; //0000100101
cout << bitset4 << endl; //0000000010101
注意:
用字符串构造时,字符串只能包含 ‘0’ 或 ‘1’ ,否则会抛出异常。
构造时,需在<>中表明bitset 的大小(即size)。
在进行有参构造时,若参数的二进制表示比bitset的size小,则在前面用0补充(如上面的栗子);若比bitsize大,参数为整数时取后面部分,参数为字符串时取前面部分(如下面栗子):
bitset<2> bitset1(12); //12的二进制为1100(长度为4),但bitset1的size=2,只取后面部分,即00
string s = "100101";
bitset<4> bitset2(s); //s的size=6,而bitset的size=4,只取前面部分,即1001
char s2[] = "11101";
bitset<4> bitset3(s2); //与bitset2同理,只取前面部分,即1110
cout << bitset1 << endl; //00
cout << bitset2 << endl; //1001
cout << bitset3 << endl; //1110
可用的操作符
bitset对于二进制有位操作符,具体如下
bitset<4> foo (string("1001"));
bitset<4> bar (string("0011"));
cout << (foo^=bar) << endl; // 1010 (foo对bar按位异或后赋值给foo)
cout << (foo&=bar) << endl; // 0010 (按位与后赋值给foo)
cout << (foo|=bar) << endl; // 0011 (按位或后赋值给foo)
cout << (foo<<=2) << endl; // 1100 (左移2位,低位补0,有自身赋值)
cout << (foo>>=1) << endl; // 0110 (右移1位,高位补0,有自身赋值)
cout << (~bar) << endl; // 1100 (按位取反)
cout << (bar<<1) << endl; // 0110 (左移,不赋值)
cout << (bar>>1) << endl; // 0001 (右移,不赋值)
cout << (foo==bar) << endl; // false (0110==0011为false)
cout << (foo!=bar) << endl; // true (0110!=0011为true)
cout << (foo&bar) << endl; // 0010 (按位与,不赋值)
cout << (foo|bar) << endl; // 0111 (按位或,不赋值)
cout << (foo^bar) << endl; // 0101 (按位异或,不赋值)
此外,可以通过 [ ] 访问元素(类似数组),注意最低位下标为0,如下:
bitset<4> foo ("1011");
cout << foo[0] << endl; //1
cout << foo[1] << endl; //1
cout << foo[2] << endl; //0
当然,通过这种方式对某一位元素赋值也是可以的,栗子就不放了。
可用函数
bitset还支持一些有意思的函数,比如:
bitset<8> foo ("10011011");
cout << foo.count() << endl; //5 (count函数用来求bitset中1的位数,foo中共有5个1
cout << foo.size() << endl; //8 (size函数用来求bitset的大小,一共有8位
cout << foo.test(0) << endl; //true (test函数用来查下标处的元素是0还是1,并返回false或true,此处foo[0]为1,返回true
cout << foo.test(2) << endl; //false (同理,foo[2]为0,返回false
cout << foo.any() << endl; //true (any函数检查bitset中是否有1
cout << foo.none() << endl; //false (none函数检查bitset中是否没有1
cout << foo.all() << endl; //false (all函数检查bitset中是全部为1
补充说明一下:test函数会对下标越界作出检查,而通过 [ ] 访问元素却不会经过下标检查,所以,在两种方式通用的情况下,选择test函数更安全一些
另外,含有一些函数:
bitset<8> foo ("10011011");
cout << foo.flip(2) << endl; //10011111 (flip函数传参数时,用于将参数位取反,本行代码将foo下标2处"反转",即0变1,1变0
cout << foo.flip() << endl; //01100000 (flip函数不指定参数时,将bitset每一位全部取反
cout << foo.set() << endl; //11111111 (set函数不指定参数时,将bitset的每一位全部置为1
cout << foo.set(3,0) << endl; //11110111 (set函数指定两位参数时,将第一参数位的元素置为第二参数的值,本行对foo的操作相当于foo[3]=0
cout << foo.set(3) << endl; //11111111 (set函数只有一个参数时,将参数下标处置为1
cout << foo.reset(4) << endl; //11101111 (reset函数传一个参数时将参数下标处置为0
cout << foo.reset() << endl; //00000000 (reset函数不传参数时将bitset的每一位全部置为0
同样,它们也都会检查下标是否越界,如果越界就会抛出异常
最后,还有一些类型转换的函数,如下:
bitset<8> foo ("10011011");
string s = foo.to_string(); //将bitset转换成string类型
unsigned long a = foo.to_ulong(); //将bitset转换成unsigned long类型
unsigned long long b = foo.to_ullong(); //将bitset转换成unsigned long long类型
cout << s << endl; //10011011
cout << a << endl; //155
cout << b << endl; //155
希望有帮助
——搜索相关——
—— BFS ——
POJ-3984___迷宫问题——BFS+路径记录
题目大意:
从 ( 1 , 1 ) (1,1) (1,1)走到 ( 5 , 5 ) (5,5) (5,5)求最短路径并输出
核心:用一道简单题记录一下模板
#include
using namespace std;
typedef long long ll;
typedef pair pii;
int dx[]= {1,-1,0,0};
int dy[]= {0,0,1,-1};
int cnt, mp[10][10];
bool vis[10][10];
stack r;
struct pot {
int u, v;
pot *pre;
};
void BFS(int u, int v) {
queue Q;
pot q, n, n_cnt[700];
q.u = u, q.v = v;
q.pre = NULL;
Q.push(q);
vis[u][v] = true;
while(!Q.empty()) {
q = Q.front();
Q.pop();
for(int i=0; i<=4; i++) {
n.u=q.u+dx[i];
n.v=q.v+dy[i];
if(n.u<1 || n.u>5 || n.v<1 || n.v>5) continue;
if(vis[n.u][n.v] || mp[n.u][n.v]) continue;
//cout<
迷宫问题——最短路径条数
求迷宫的最短路径条数有多少条,一般方法会超时,这里记录一下模板
简要思路:用 a n s ans ans记录到每个点的条数,当新到达的点的新距离小于就旧的距离时,更新新点的 a n s ans ans;若新距离等于旧距离,也就是可能是最短路径的时候,就 a n s 新 + = a n s 旧 ans_{新} += ans_{旧} ans新+=ans旧
P S : PS: PS:若新旧距离相等,则不需要再将该点加入到队列中,因为这个点已经在队列里了
优先队列:
#include
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll mod = 1000000007;
int dx[4] = {0,1,0,-1};
int dy[4] = {1,0,-1,0};
int r, c, edx, edy;
int dis[505][505], maze[505][505];
char mp[505][505];
ll ans[505][505];
bool vis[505][505];
struct Point {
int x, y, t;
bool operator<(const Point & b) const {
return t>b.t;
}
Point(){}
Point(int x,int y,int t):x(x),y(y),t(t) {}
} p;
void BFS() {
dis[1][1] = 0;
ans[1][1] = 1;
priority_queue pq;
pq.push(Point(1,1,0));
while(!pq.empty()) {
int nx, ny;
//找点
Point cur = pq.top(); pq.pop();
if(vis[cur.x][cur.y] == 1) continue;
vis[cur.x][cur.y] = 1;
for(int k=0; k<4; k++) {
nx = cur.x + dx[k];
ny = cur.y + dy[k];
if(vis[nx][ny] == 1) continue;
if(nx<1 || nx>r || ny<1 || ny>c) continue;
int t = dis[cur.x][cur.y] + maze[nx][ny];
if(dis[nx][ny] > t) {
dis[nx][ny] = t;
ans[nx][ny] = ans[cur.x][cur.y]%mod;
pq.push(Point(nx, ny, dis[nx][ny]));
}
else if(dis[nx][ny] == t) {
ans[nx][ny] += ans[cur.x][cur.y];
ans[nx][ny] %= mod;
//pq.push(Point(nx, ny, dis[nx][ny]));
}
}
}
if(dis[edx][edy] == INF) printf("-1\n");
else printf("%lld\n", ans[edx][edy]%mod);
}
void init() {
memset(vis,0,sizeof(vis));
memset(ans,0,sizeof ans);
memset(maze,INF,sizeof(maze));
memset(dis,INF,sizeof(dis));
}
int main(){
int cas;
scanf("%d", &cas);
while(cas--) {
scanf("%d%d", &r, &c);
init();
for(int i=1; i<=r; i++)
scanf("%s", mp[i]+1);
for(int i=1; i<=r; i++)
for(int j=1; j<=c; j++)
if(mp[i][j] == '.')
maze[i][j] = 1;
scanf("%d%d", &edx, &edy);
BFS() ;
}
}
Gym - 101291C___Buggy Robot —— DP + BFS
题目大意:
给你一张 n ∗ m n*m n∗m 的图,有起点和终点,然后给你一个操作序列,由 U D L R UDLR UDLR 组成,代表上下左右移动
现在在这个已有的操作序列上,从起点开始,如果碰到墙壁或越界,则原地不动,否则前进
如果按照这个序列走下去走不到终点,那么可以在这个序列上进行增加或者删除的操作,每增加和删除一次算作一次操作,求走到终点需要的最少的操作数,到达终点指任何时候都可以
解题思路:
明显有一个BFS的过程,但是无法处理到达每个点时,现有的最少的操作数
那么有了这个思想,用dp来处理增加和删除的操作再好不过,用 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 代表到达 ( i , j ) (i,j) (i,j)时,序列处理到 k k k 时,需要的最少操作数
所有有三个状态转移的过程,如下:
增加: d p [ n . x ] [ n . y ] [ n o w ] = m i n ( d p [ q . x ] [ q . y ] [ n o w ] + 1 , d p [ n . x ] [ n . y ] [ n o w ] ) dp[n.x][n.y][now] = min(dp[q.x][q.y][now]+1, dp[n.x][n.y][now]) dp[n.x][n.y][now]=min(dp[q.x][q.y][now]+1,dp[n.x][n.y][now])
删除: d p [ q . x ] [ q . y ] [ n o w ] = m i n ( d p [ q . x ] [ q . y ] [ n o w ] + 1 , d p [ q . x ] [ q . y ] [ n o w ] ) dp[q.x][q.y][now] = min(dp[q.x][q.y][now]+1, dp[q.x][q.y][now]) dp[q.x][q.y][now]=min(dp[q.x][q.y][now]+1,dp[q.x][q.y][now])
取当前序列的操作: d p [ n . x ] [ n . y ] [ n o w + 1 ] = m i n ( d p [ q . x ] [ q . y ] [ n o w ] + 1 , d p [ n . x ] [ n . y ] [ n o w + 1 ] ) dp[n.x][n.y][now+1] = min(dp[q.x][q.y][now]+1, dp[n.x][n.y][now+1]) dp[n.x][n.y][now+1]=min(dp[q.x][q.y][now]+1,dp[n.x][n.y][now+1])
PS:注意 n o w = 0 now=0 now=0 的时候,代表不取操作数从起点到终点
代码思路:
用dp的大小来判断即可,不需要vis
核心:处理到dp的转移
#include
using namespace std;
typedef long long ll;
int dx[]= {0,1,0,-1};
int dy[]= {1,0,-1,0};
int nn, mm, sx, sy, ex, ey;
int now, dp[55][55][55];
char mp[55][55], s[55];
struct Node {
int x, y, dep;
} ;
void bfs() {
Node q, n;
queue Q;
q.x = sx, q.y = sy, q.dep = 0;
dp[sx][sy][0] = 0;
Q.push(q);
while(!Q.empty()) {
q = Q.front();
Q.pop();
now = q.dep;
for(int i=0; i<4; i++) { // 添加
n.x = q.x + dx[i];
n.y = q.y + dy[i];
n.dep = now;
if(n.x<1||n.x>nn||n.y<1||n.y>mm) continue;
if(mp[n.x][n.y]=='#') continue;
if(dp[n.x][n.y][now] > dp[q.x][q.y][now]+1) {
dp[n.x][n.y][now] = dp[q.x][q.y][now]+1;
Q.push(n);
}
}
if(now dp[q.x][q.y][now]+1) { // 删除
dp[q.x][q.y][now+1] = dp[q.x][q.y][now]+1;
n.x = q.x, n.y = q.y;
n.dep = now+1;
Q.push(n);
}
if(s[now+1]=='L') n.x = q.x, n.y = q.y-1;
if(s[now+1]=='R') n.x = q.x, n.y = q.y+1;
if(s[now+1]=='U') n.x = q.x-1, n.y = q.y;
if(s[now+1]=='D') n.x = q.x+1, n.y = q.y;
if(n.x<1||n.x>nn||n.y<1||n.y>mm) n.x = q.x, n.y = q.y;
if(mp[n.x][n.y]=='#') n.x = q.x, n.y = q.y;
if(dp[n.x][n.y][now+1] > dp[q.x][q.y][now]) {
dp[n.x][n.y][now+1] = dp[q.x][q.y][now];
n.dep = now+1;
Q.push(n);
}
}
}
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
nn = n, mm = m;
getchar();
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
scanf("%c", &mp[i][j]);
if(mp[i][j]=='R') sx=i, sy=j;
if(mp[i][j]=='E') ex=i, ey=j;
}
getchar();
}
scanf("%s", s+1);
memset(dp, 0x3f, sizeof(dp));
bfs();
int ans = INT_MAX;
for(int i=0; i<=strlen(s+1); i++)
ans = min(ans, dp[ex][ey][i]);
printf("%d\n", ans);
}
牛客第八场 D Distance —— 定期重构 BFS
题目大意:
一个 n ∗ m ∗ h n*m*h n∗m∗h 的立方体
两个操作:
1 1 1 x x x y y y z z z 在这个立方体上标记一个点
2 2 2 x x x y y y z z z 求离这个点最近的标记点的曼哈顿距离
解题思路:
首先对于多个标记点,可以一次 O ( n m h ) O(nmh) O(nmh) 的多源 B F S BFS BFS 处理到每个点的距离,这样查询是 O ( 1 ) O(1) O(1) 的
假设已经标记了 K K K 个点,暴力求查询点的最近点是 O ( K ) O(K) O(K) 的
考虑将两者结合起来,将标记点放到一个栈里,定期重构 B F S BFS BFS
设置阈值 E E E ,当标记点个数 K < E KK<E 时,采用暴力查询, O ( q E ) O(qE) O(qE)
当标记点个数 K > = E K>=E K>=E 时,重构一次 B F S BFS BFS , O ( q n m h / E ) O(qnmh/E) O(qnmh/E)
重构之后,相当于这个 K K K 个点已经更新到了答案之中,清空栈,可以理解为分块 B F S BFS BFS
则复杂度为 O ( q n m h / E ) + O ( q E ) O(qnmh/E) + O(qE) O(qnmh/E)+O(qE),当 E = n m h E = \sqrt{nmh} E=nmh ,取得最小值
时间复杂度: O ( n m h + q n m h ) O(nmh + q\sqrt{nmh}) O(nmh+qnmh )
核心:定期重构,分块BFS
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
//#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
typedef long long ll;
using pii = pair ;
const int maxn = 2e5 + 5;
int n, m, h, q, dis[maxn];
int to[10][5] = {{0,0,1},{0,0,-1},{0,1,0},{0,-1,0},{1,0,0},{-1,0,0}};
struct node{
int x, y, z;
int step;
};
vector s;
int getid(int x, int y, int z){
return n*m*(z-1) + n*(y-1) + x;
}
int getdis(node a, node b){
return abs(a.x-b.x) + abs(a.y-b.y) + abs(a.z-b.z);
}
void bfs(){
queue Q;
for(int i=0; in || nd.y>m || nd.z>h) continue;
if(dis[getid(nd.x, nd.y, nd.z)] <= nd.step) continue;
dis[getid(nd.x, nd.y, nd.z)] = nd.step;
Q.push(nd);
}
}
}
int main() {
scanf("%d%d%d%d", &n, &m, &h, &q);
int sq = 2;
memset(dis, 0x3f3f3f, sizeof(dis));
while(q--){
int o, x, y, z;
scanf("%d%d%d%d", &o, &x, &y, &z);
if(o & 1){
s.push_back(node{x,y,z,0});
if(s.size() > sq) bfs();
} else {
int ans = dis[getid(x, y, z)];
for(int i=0; i
2019百度之星 C Mindis —— 离散区间 + BFS
解题思路:
将整个图离散化一下,注意:
由于一个矩形是一个区间,我们要考虑的是区间内的速度
所以离散化时不能左闭右开,而是计算速度的时候左闭右开
计算速度有四个方向的速度,这里只用了两个方向
BFS的过程中特判一下速度的方向即可
核心:离散区间后跑BFS
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
using pii = pair ;
const int maxn = 2e3 + 5;
const double eps = 1e-7;
int tx[5] = {1,-1,0,0};
int ty[5] = {0,0,1,-1};
int T, n, xa, ya, xb, yb, sx, sy;
int spe1[maxn][maxn], spe2[maxn][maxn];
double ans, Time[maxn][maxn];
vector lsx, lsy;
struct Node{
int x1, y1, x2, y2;
} a[maxn];
struct node{
int x, y;
double ti;
bool operator <(const node &A)const{
return ti > A.ti;
}
} ;
priority_queue Q;
int getidx(int x){
return lower_bound(lsx.begin(), lsx.end(), x) - lsx.begin();
}
int getidy(int x){
return lower_bound(lsy.begin(), lsy.end(), x) - lsy.begin();
}
void bfs(int stx, int sty){
while(!Q.empty()) Q.pop();
node q, tmp;
q.x = stx, q.y = sty, q.ti = 0;
Q.push(q);
Time[q.x][q.y] = 0;
while(!Q.empty()){
q = Q.top();
Q.pop();
if(fabs(q.ti-Time[q.x][q.y]) > eps) continue;
if(q.x==xb && q.y==yb) {
ans = q.ti;
return;
}
for(int i=0; i<4; i++){
int nx = q.x + tx[i];
int ny = q.y + ty[i];
if(nx<0 || ny<0 || nx>=sx || ny>=sy) continue;
double speed1, speed2, new_ti;
int disx = abs(lsx[nx] - lsx[q.x]);
int disy = abs(lsy[ny] - lsy[q.y]);
if(tx[i] == 0) {
if(ty[i]>0) speed2 = spe2[q.x][q.y];
else speed2 = spe2[nx][ny];
new_ti = disy / speed2 + q.ti;
}
if(ty[i] == 0) {
if(tx[i]>0) speed1 = spe1[q.x][q.y];
else speed1 = spe1[nx][ny];
new_ti = disx / speed1 + q.ti;
}
if(new_ti >= Time[nx][ny]) continue;
Time[nx][ny] = new_ti;
tmp.x = nx, tmp.y = ny, tmp.ti = new_ti;
Q.push(tmp);
}
}
}
int main() {
scanf("%d", &T);
while(T--){
ans = 0;
scanf("%d", &n);
memset(spe1, 0, sizeof(spe1));
memset(spe2, 0, sizeof(spe2));
lsx.clear(), lsy.clear();
for(int i=1; i<=n; i++){
scanf("%d%d", &a[i].x1, &a[i].y1);
scanf("%d%d", &a[i].x2, &a[i].y2);
lsx.push_back(a[i].x1), lsx.push_back(a[i].x2);
lsy.push_back(a[i].y1), lsy.push_back(a[i].y2);
}
scanf("%d%d%d%d", &xa, &ya, &xb, &yb);
lsx.push_back(xa), lsx.push_back(xb);
lsy.push_back(ya), lsy.push_back(yb);
sort(lsx.begin(),lsx.end());
sort(lsy.begin(),lsy.end());
lsx.erase(unique(lsx.begin(), lsx.end()), lsx.end());
lsy.erase(unique(lsy.begin(), lsy.end()), lsy.end());
sx = lsx.size(), sy = lsy.size();
for(int i=0; i
—— DFS ——
POJ-3126___Prime Path——DFS+思维
题目大意:
给你 m m m个数,每个数都不超过 2 n 2^n 2n,对这 m m m个数,若 x x x& y = 0 y=0 y=0,则 x x x与 y y y属于一个连通集,问一共有几个连通集???
解题思路:
显然,暴力是超时的,考虑到与操作,对于 x x x& y = 0 y=0 y=0,若我们先把所有& x = 0 x=0 x=0的数先求出来,若后面的数与我们之前求出来的数相等,则说明他们不属于一个连通集
因此,若 i i i& x = 0 x=0 x=0,那么 i = x i=x i=x ^ ( 1 < < n ) − 1 (1<(1<<n)−1,同时,若将i的二进制中的 1 1 1改为 0 0 0,也满足& x = 0 x=0 x=0,那么就要求i的所有子集,就用到DFS
代码思路:
一开始我只是先求 i i i,然后对 i i i DFS,遍历的时候直接判断,发现答案多了很多连通集,细细思考后发现有蹊跷,这里贴出两份代码,第二份是考虑到了后续的 a [ i ] a[i] a[i]的补集被忽略掉了的正确代码,直接看代码容易理解
核心:思维、从位运算中转化到DFS,有点东西
#include
using namespace std;
int n, m, cnt, all;
bool vis[1<<23], have[1<<23];
int a[1<<23];
void DFS(int k) {
vis[k] = true;
if(!vis[k ^ all] && have[k])
DFS(k ^ all);
for(int i=0; i
HDU-1078___FatMouse and Cheese —— 解题报告 记忆化搜索
Sample Input
3 1 1 2 5 10 11 6 12 12 7 -1 -1
Sample Output
37
题目大意:
给出示例,一个n*n的地图,每个位置放有香甜的奶酪!老鼠起始在(1,1)每次最多走k步,每次移动只能水平或垂直移动。
问老鼠最多能吃的奶酪为多少?
解题思路:
类似于贪心,但还是动态规划范畴,用dp数组剪枝即可
代码思路:
1、用dp数组存放该地址最多能吃到的奶酪,并且当其值为0时跳过
2、用另一个数组来实现每次能移动多步!
3、递归实现存放dp数组里的期望值
核心:
ans = max(ans,dfs(xx,yy)) 状态转移在这里实现不断递归,最终得到最后一个位置也是最大值的值,然后在不断返回将对应的答案记录在dp数组里;
#include
using namespace std;
int dp[105][105], a[105][105];
int to[4][2] = {1,0,-1,0,0,1,0,-1};
int n,k;
int dfs(int x,int y) {
int ans=0;
if(!dp[x][y]) {
for(int i=1; i<=k; i++) {
for(int j = 0; j<4; j++) {
int xx = x+to[j][0]*i;
int yy = y+to[j][1]*i;
if(xx>=1&&yy>=1&&xx<=n&&yy<=n&&a[xx][yy]>a[x][y])
ans = max(ans,dfs(xx,yy));
}
dp[x][y]=ans+a[x][y];
}
}
return dp[x][y];
}
int main() {
std::ios::sync_with_stdio(false);
while(cin>>n>>k&&n>0&&k>0) {
memset(a,0,sizeof(a));
memset(dp,0,sizeof(dp));
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
cin>>a[i][j];
cout<
——舞蹈链——
舞蹈链——重复覆盖 模板
#include
using namespace std;
typedef long long ll;
#define N 100010
#define M 1010
struct DancingLink{
int n,s,ansd;//列数 节点总数
int S[M],A[M],H[M];//S[]该列节点总数 A[]答案 H[]行首指针
int L[N],R[N],U[N],D[N]; //L[],R[],U[],D[] 上下左右
int X[N],C[N],vis[M];//X[] C[] 行列编号
void init(int n){//初始化
this->n=n;
for(int i=0;i<=n;i++)
U[i]=i,D[i]=i,L[i]=i-1,R[i]=i+1;
R[n]=0,L[0]=n;s=n+1;
memset(S,0,sizeof(S));
memset(H,-1,sizeof(H));
}
void DelCol(int c){//删除列
for(int i=D[c];i!=c;i=D[i])
L[R[i]]=L[i],R[L[i]]=R[i];
}
void ResCol(int c){//恢复列
for(int i=U[c];i!=c;i=U[i])
L[R[i]]=R[L[i]]=i;
}
void AddNode(int r,int c){//添加节点
++S[c],C[++s]=c,X[s]=r;
D[s]=D[c],U[D[c]]=s,U[s]=c,D[c]=s;
if(H[r]<0) H[r]=L[s]=R[s]=s;//行首节点
else R[s]=R[H[r]],L[R[H[r]]]=s,L[s]=H[r],R[H[r]]=s;
}
int f(){
int ret=0;
memset(vis,0,sizeof(vis));
for(int i=R[0];i;i=R[i])
if(!vis[i]){
ret++;vis[i]=1;
for(int j=D[i];j!=i;j=D[j])
for(int k=R[j];k!=j;k=R[k])
vis[C[k]]=1;
}
return ret;
}
void dfs(int d){//深度,深搜遍历
if(d+f()>=ansd) return;
if(!R[0]){
if(d
HDU-5046___Airport——二分 + DLX +剪枝
题目大意:
给定 n n n 个城市的坐标,要在这 n n n 个城市里建 k k k 个飞机场,要使任意城市离最近的飞机场距离的最大值最小
解题思路:
最大值最小化,很典型的二分,即二分城市与飞机场的距离,若满足 k k k 个以内的飞机场在这个距离内能覆盖所有城市,则减小这个二分值
那么我们就解决了怎么得到这个最小值,但问题的关键是怎么去判断k个飞机场能否覆盖所有城市,这里的思路几乎与HDU 2295是同一个套路
我们用二分的距离去判断可行性,若在两座城市的距离在这个距离内,那么就在DLX模型中建边,则转化为了可重复覆盖的问题
代码思路:
如果直接套模板的话不知道能不能过,在本题内有两个除模板以外的两个优化:
①:因为我们要判断的是能否在 k k k 行内覆盖完,所以在 dfs 的过程中对深度与k进行比较,加速返回
②:在本题中直接对距离的最大值进行二分是不足够好的,看了别人的题解之后发现可以对所有的距离值进行二分,思路是:先把所有城市的距离用数组存下来,排序+去重,然后直接二分数组下标即可,极大的优化了时间复杂度
核心:二分 + DLX 的好题,对优化要做到位
#include
using namespace std;
typedef long long ll;
int n,k,cas,t=1;
pair city[65];
ll dis[65][65], val[3650];
#define N 3650
#define M 100
struct DancingLink {
int m,s,ansd;//列数 节点总数
int S[M],A[M],H[M];//S[]该列节点总数 A[]答案 H[]行首指针
int L[N],R[N],U[N],D[N]; //L[],R[],U[],D[] 上下左右
int X[N],C[N],vis[M];//X[] C[] 行列编号
void init(int n) { //初始化
for(int i=0; i<=n; i++)
U[i]=i,D[i]=i,L[i]=i-1,R[i]=i+1;
R[n]=0,L[0]=n;s=n+1;
memset(S,0,sizeof(S));
memset(H,-1,sizeof(H));
}
void DelCol(int c) { //删除列
for(int i=D[c]; i!=c; i=D[i])
L[R[i]]=L[i],R[L[i]]=R[i];
}
void ResCol(int c) { //恢复列
for(int i=U[c]; i!=c; i=U[i])
L[R[i]]=R[L[i]]=i;
}
void AddNode(int r,int c) { //添加节点
++S[c],C[++s]=c,X[s]=r;
D[s]=D[c],U[D[c]]=s,U[s]=c,D[c]=s;
if(H[r]<0) H[r]=L[s]=R[s]=s;//行首节点
else R[s]=R[H[r]],L[R[H[r]]]=s,L[s]=H[r],R[H[r]]=s;
}
int f() {
int ret=0;
memset(vis,0,sizeof(vis));
for(int i=R[0]; i; i=R[i])
if(!vis[i]) {
ret++;vis[i]=1;
for(int j=D[i]; j!=i; j=D[j])
for(int k=R[j]; k!=j; k=R[k])
vis[C[k]]=1;
}
return ret;
}
bool dfs(int d) { //深度,深搜遍历
if(d+f()>k) return 0;
if(!R[0]) return d<=k;
int c=R[0];
for(int i=R[0]; i; i=R[i]) if(S[i]
HDU-4069___Squiggly Sudoku——锯齿数独 BFS + DLX
题目大意:
给你一个锯齿数独的图,每个凹凸形状的宫的上下左右边界以及格子里的数字都给出相应的计算规则,要你求这个数独的唯一解,或者输出 0 0 0 解或多解
解题思路:
数独模板题,关键在于宫的变换,我们回想之前处理数独的宫的方法,首先要对每个数字所处在的宫进行编号,所以用BFS对每个宫进行搜索编号,然后直接将编好号的图建立模型
这里因为会存在多解,所以要对DLX精准覆盖里的dfs进行修改,在第一次找到答案的时候一定要把答案记录下来,不然在后续的搜索过程中会将答案覆盖掉
代码思路:
BFS对图编号后进行DLX精准覆盖
核心:对数独的宫进行合理化处理
#include
using namespace std;
typedef long long ll;
#define N 500010
#define M 1010
int flag;
struct DancingLink {
int n,s,ansd,ansd_real;//列数 节点总数
int S[M],A[M],H[M],A_real[M];//S[]该列节点总数 A[]答案 H[]行首指针
int L[N],R[N],U[N],D[N]; //L[],R[],U[],D[] 上下左右
int X[N],C[N];//X[] C[] 行列编号
void init(int n) { //初始化
this->n=n;
for(int i=0; i<=n; i++)
U[i]=i,D[i]=i,L[i]=i-1,R[i]=i+1;
R[n]=0,L[0]=n;s=n+1;
memset(S,0,sizeof(S));
memset(H,-1,sizeof(H));
}
void DelCol(int c) { //删除列
L[R[c]]=L[c];R[L[c]]=R[c];
for(int i=D[c]; i!=c; i=D[i])
for(int j=R[i]; j!=i; j=R[j])
U[D[j]]=U[j],D[U[j]]=D[j],--S[C[j]];
}
void ResCol(int c) { //恢复列
for(int i=U[c]; i!=c; i=U[i])
for(int j=L[i]; j!=i; j=L[j])
++S[C[j]],U[D[j]]=j,D[U[j]]=j;
L[R[c]]=c,R[L[c]]=c;
}
void AddNode(int r,int c) { //添加节点
++S[c],C[++s]=c,X[s]=r;
D[s]=D[c],U[D[c]]=s,U[s]=c,D[c]=s;
if(H[r]<0) H[r]=L[s]=R[s]=s;//行首节点
else R[s]=R[H[r]],L[R[H[r]]]=s,L[s]=H[r],R[H[r]]=s;
}
void dfs(int d) { //深度,深搜遍历
if(flag>1) return;
if(!R[0]) {
flag++;if(flag>1) return;
ansd_real = d;
for(int i=0; i> Q;
void BFS(int n, int m){
while(!Q.empty()) Q.pop();
Q.push(make_pair(n,m));
id[n][m] = nid;
while(!Q.empty()){
int q1 = Q.front().first;
int q2 = Q.front().second;
Q.pop();
for(int i=0; i<4; i++){
int x = q1 + dx[i];
int y = q2 + dy[i];
if(wall[q1][q2][i] || id[x][y]) continue;
id[x][y] = nid;
Q.push(make_pair(x,y));
}
}
nid++;
}
int main() {
int t, cas = 1;
scanf("%d", &t);
while(t--) {
for(int i=1; i<=9; i++)
for(int j=1; j<=9; j++)
scanf("%d", &mp[i][j]);
dlx.init(9*9*4);
memset(wall,0,sizeof(wall));
for(int i=1; i<=9; i++)
for(int j=1; j<=9; j++) {
if(mp[i][j]>=128) //左墙
mp[i][j]-=128, wall[i][j][0]=1;
if(mp[i][j]>=64) //下墙
mp[i][j]-=64, wall[i][j][3]=1;
if(mp[i][j]>=32) //右墙
mp[i][j]-=32, wall[i][j][1]=1;
if(mp[i][j]>=16) //上墙
mp[i][j]-=16, wall[i][j][2]=1;
}
nid = 1;
memset(id,0,sizeof(id));
for(int i=1; i<=9; i++)
for(int j=1; j<=9; j++)
if(!id[i][j]) BFS(i,j);
for(int i=1; i<=9; i++)
for(int j=1; j<=9; j++)
for(int k=1; k<=9; k++)
if(!mp[i][j] || mp[i][j]==k) {
int t = encode(0,i,j);
int r = encode(0,t,k);
dlx.AddNode(r,t);
dlx.AddNode(r,encode(1,i,k));
dlx.AddNode(r,encode(2,j,k));
dlx.AddNode(r,encode(3,id[i][j],k));
}
flag = dlx.ansd_real = 0;
dlx.dfs(0);
decode();
printf("Case %d:\n", cas++);
if(dlx.ansd_real == 0)
printf("No solution\n");
else if(flag>1)
printf("Multiple Solutions\n");
else {
for(int i=1; i<=9; i++){
for(int j=1; j<=9; j++)
printf("%d",ans[i][j]);
puts("");
}
}
}
}
舞蹈链——精确覆盖 模板
#include
using namespace std;
typedef long long ll;
#define N 500010
#define M 1010
struct DancingLink{
int n,s,ansd;//列数 节点总数
int S[M],A[M],H[M];//S[]该列节点总数 A[]答案 H[]行首指针
int L[N],R[N],U[N],D[N]; //L[],R[],U[],D[] 上下左右
int X[N],C[N];//X[] C[] 行列编号
void init(int n){//初始化
this->n=n;
for(int i=0;i<=n;i++)
U[i]=i,D[i]=i,L[i]=i-1,R[i]=i+1;
R[n]=0,L[0]=n;s=n+1;
memset(S,0,sizeof(S));
memset(H,-1,sizeof(H));
}
void DelCol(int c){//删除列
L[R[c]]=L[c];R[L[c]]=R[c];
for(int i=D[c];i!=c;i=D[i])
for(int j=R[i];j!=i;j=R[j])
U[D[j]]=U[j],D[U[j]]=D[j],--S[C[j]];
}
void ResCol(int c){//恢复列
for(int i=U[c];i!=c;i=U[i])
for(int j=L[i];j!=i;j=L[j])
++S[C[j]],U[D[j]]=j,D[U[j]]=j;
L[R[c]]=c,R[L[c]]=c;
}
void AddNode(int r,int c){//添加节点
++S[c],C[++s]=c,X[s]=r;
D[s]=D[c],U[D[c]]=s,U[s]=c,D[c]=s;
if(H[r]<0) H[r]=L[s]=R[s]=s;//行首节点
else R[s]=R[H[r]],L[R[H[r]]]=s,L[s]=H[r],R[H[r]]=s;
}
bool dfs(int d){//深度,深搜遍历
if(!R[0]){
ansd=d;return true;
}
int c=R[0];
for(int i=R[0];i;i=R[i]) if(S[i]
舞蹈链 —— 9 × 9 数独模板(POJ - 3074)
用 324 324 324 列、 729 729 729 行构造:
P S : PS: PS:这里其实还可以优化,对必选行的列进行处理,可以减少行和列
#include
using namespace std;
typedef long long ll;
#define N 500010
#define M 1010
struct DancingLink {
int n,s,ansd;//列数 节点总数
int S[M],A[M],H[M];//S[]该列节点总数 A[]答案 H[]行首指针
int L[N],R[N],U[N],D[N]; //L[],R[],U[],D[] 上下左右
int X[N],C[N];//X[] C[] 行列编号
void init(int n) { //初始化
this->n=n;
for(int i=0; i<=n; i++)
U[i]=i,D[i]=i,L[i]=i-1,R[i]=i+1;
R[n]=0,L[0]=n;s=n+1;
memset(S,0,sizeof(S));
memset(H,-1,sizeof(H));
}
void DelCol(int c) { //删除列
L[R[c]]=L[c];R[L[c]]=R[c];
for(int i=D[c]; i!=c; i=D[i])
for(int j=R[i]; j!=i; j=R[j])
U[D[j]]=U[j],D[U[j]]=D[j],--S[C[j]];
}
void ResCol(int c) { //恢复列
for(int i=U[c]; i!=c; i=U[i])
for(int j=L[i]; j!=i; j=L[j])
++S[C[j]],U[D[j]]=j,D[U[j]]=j;
L[R[c]]=c,R[L[c]]=c;
}
void AddNode(int r,int c) { //添加节点
++S[c],C[++s]=c,X[s]=r;
D[s]=D[c],U[D[c]]=s,U[s]=c,D[c]=s;
if(H[r]<0) H[r]=L[s]=R[s]=s;//行首节点
else R[s]=R[H[r]],L[R[H[r]]]=s,L[s]=H[r],R[H[r]]=s;
}
bool dfs(int d) { //深度,深搜遍历
if(!R[0]) {
ansd=d;return true;
}
int c=R[0];
for(int i=R[0]; i; i=R[i]) if(S[i]>str;
if(str=="end") break;
int flag = 0, mp[100][100];
for(int i=1; i<=9; i++)
for(int j=1; j<=9; j++) {
ch = str[flag++];
if(ch=='.') mp[i][j] = 0;
else mp[i][j] = ch - '0';
}
dlx.init(9*9*4);
for(int i=1; i<=9; i++)
for(int j=1; j<=9; j++)
for(int k=1; k<=9; k++)
if(!mp[i][j] || mp[i][j]==k) {
int t = encode(0,i,j);
int r = encode(0,t,k);
dlx.AddNode(r,t);
dlx.AddNode(r,encode(1,i,k));
dlx.AddNode(r,encode(2,j,k));
dlx.AddNode(r,encode(3,(i-1)/3*3+(j+2)/3,k));
}
dlx.dfs(0);
decode();
if(dlx.ansd == 0)
printf("impossible\n");
else {
for(int i=1; i<=9; i++)
for(int j=1; j<=9; j++)
printf("%d",ans[i][j]);
puts("");
}
}
}
舞蹈链 —— M × M 数独模板
数独为M × M,则有M × M × M行 、M × M × 4列
下面是数独题任意大小模板:
#include
using namespace std;
const int M=16;//数独大小
const int MN=M*M*M+10;
const int MM=M*M*4+10;
const int MNN=MN*4+MM; //最大点数,数独问题节点没有那么多
struct DLX {
int n,m,si;//n行数m列数si目前有的节点数
int U[MNN],D[MNN],L[MNN],R[MNN],Row[MNN],Col[MNN];
int H[MN],S[MM]; //记录行的选择情况和列的覆盖情况
int ansd,ans[MN];
void init(int _n,int _m) { //初始化空表
n=_n;m=_m;
for(int i=0; i<=m; i++) { //初始化第一横行(表头)
S[i]=0;U[i]=D[i]=i;L[i]=i-1;R[i]=i+1;
}
R[m]=0;L[0]=m;si=m;
for(int i=1; i<=n; i++) H[i]=-1;
}
void link(int r,int c) {
++S[Col[++si]=c];Row[si]=r;
D[si]=D[c];U[D[c]]=si;U[si]=c;D[c]=si;
if(H[r]<0) H[r]=L[si]=R[si]=si;
else {
R[si]=R[H[r]];L[R[H[r]]]=si;
L[si]=H[r];R[H[r]]=si;
}
}
void remove(int c) { //列表中删掉c列
L[R[c]]=L[c];R[L[c]]=R[c];
for(int i=D[c]; i!=c; i=D[i])
for(int j=R[i]; j!= i; j=R[j])
U[D[j]]=U[j],D[U[j]]=D[j],--S[Col[j]];
}
void resume(int c) { //恢复c列
for(int i=U[c]; i!=c; i=U[i])
for(int j=L[i]; j!=i; j=L[j])
++S[Col[U[D[j]]=D[U[j]]=j]];
L[R[c]]=R[L[c]]=c;
}
bool dance(int d) {
if(R[0]==0) {
ansd=d;return 1;
}
int c=R[0];
for(int i=R[0]; i!=0; i=R[i])if(S[i]
HDU-2295___Radar——二分 + DLX重复覆盖
题目大意:
n n n 个城市、 m m m 个雷达,最多可以用 k k k 个雷达,然后给出城市和雷达的坐标,要求雷达的最小覆盖半径,使得所有城市都被雷达覆盖???
解题思路:
很清晰是一道DLX重复覆盖的题,本题的精度要精确到 1 e − 8 1e-8 1e−8,所以只能用二分,二分的可行性判断是所用的雷达数是否 ≤ k ≤k ≤k
本题建造 01 01 01 矩阵比较简单,横坐标就是雷达,纵坐标为城市,雷达与城市之间的距离若 ≤ m i d ≤mid ≤mid,则该点为 1 1 1
代码思路:
主要还是重复覆盖的模板题,注意是用了 A* 的优化,估价函数 f f f 意义为从当前状态最好情况下需要添加几条边才能覆盖
核心:二分 + DLX 的简单题,同时注意精度
P S : PS: PS:本题的 dfs 返回值可以为 b o o l bool bool,因为可以用当前深度与 k k k 进行比较
#include
using namespace std;
typedef long long ll;
#define N 3020
#define M 55
struct DancingLink{
int n,s,ansd;//列数 节点总数
int S[M],A[M],H[M];//S[]该列节点总数 A[]答案 H[]行首指针
int L[N],R[N],U[N],D[N]; //L[],R[],U[],D[] 上下左右
int X[N],C[N],vis[M];//X[] C[] 行列编号
void init(int n){//初始化
this->n=n;
for(int i=0;i<=n;i++)
U[i]=i,D[i]=i,L[i]=i-1,R[i]=i+1;
R[n]=0,L[0]=n;s=n+1;
memset(S,0,sizeof(S));
memset(H,-1,sizeof(H));
}
void DelCol(int c){//删除列
for(int i=D[c];i!=c;i=D[i])
L[R[i]]=L[i],R[L[i]]=R[i];
}
void ResCol(int c){//恢复列
for(int i=U[c];i!=c;i=U[i])
L[R[i]]=R[L[i]]=i;
}
void AddNode(int r,int c){//添加节点
++S[c],C[++s]=c,X[s]=r;
D[s]=D[c],U[D[c]]=s,U[s]=c,D[c]=s;
if(H[r]<0) H[r]=L[s]=R[s]=s;//行首节点
else R[s]=R[H[r]],L[R[H[r]]]=s,L[s]=H[r],R[H[r]]=s;
}
int f(){
int ret=0;
for(int i=R[0];i;i=R[i]) vis[i]=0;
for(int i=R[0];i;i=R[i])
if(!vis[i]){
ret++;vis[i]=1;
for(int j=D[i];j!=i;j=D[j])
for(int k=R[j];k!=j;k=R[k])
vis[C[k]]=1;
}
return ret;
}
void dfs(int d){//深度,深搜遍历
if(d+f()>=ansd) return;
if(!R[0]){
ansd=min(d,ansd);
return;
}
int c=R[0];
for(int i=R[0];i;i=R[i]) if(S[i]=eps){
dlx.init(n);
mid = (l+r)/2;
for(int i=0; i
——动态规划——
—— 递推 ——
最大子段和___O(n)——递推dp
给定 n n n 个整数(可能为负数)组成的序列 a [ 1 ] , a [ 2 ] , a [ 3 ] , … , a [ n ] a[1],a[2],a[3],…,a[n] a[1],a[2],a[3],…,a[n],求该序列如 a [ i ] + a [ i + 1 ] + … + a [ j ] a[i]+a[i+1]+…+a[j] a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为 0 0 0
输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20
#include
using namespace std;
typedef long long ll;
int n, a[10005], dp[10005];
int ans, k;
int main(){
scanf("%d", &n);
for(int i=0; i0) k += a[i];
else k=a[i];
if(i) dp[i] = max(dp[i-1], k);
}
printf("%d\n", dp[n-1]);
}
奇奇的幸福度 dp + 最长路
Problem G: 奇奇的幸福度
Problem G: 奇奇的幸福度
Time Limit: 5 Sec
Memory Limit: 128 MB
Submit: 15
Solved: 1
[Submit][Status]Web Board][Creator:
]
Description
在G国,有N个城市,编号从1到N。
奇奇住在城市1,他打算去城市N。问题是他要决定他的路线。
经过一番研究,奇奇发现,如果他的路线经过城市i(包括出发城市和终点城市),通过在这个城市游玩,他的幸福度会增长C[i]。然而奇奇也是个原则的人,如果他现在处于城市i,那么下一个去的城市的编号必须大于i。
而在交通工具方面,奇奇在每个非终点城市i,都可以有两种选择:
1、 骑着自己的宝马到城市i+1,由于是自己骑马,已经没有啥新鲜感了,所以幸福度不增加;
2、 如果城市i有飞往其它城市的航班,奇奇可以选择乘坐飞机到其它城市,不同的航班也会使奇奇的幸福度得到上升。一个城市可能有多个航班,也可能没有。但即使是坐飞机,航班的目的城市编号也是要大于起点城市编号。不用担心钱,不是问题。
出发前奇奇的幸福度是0。
那么问题来了,奇奇到城市N的幸福度最大是增长多少。
额,天真。
奇奇收到消息,G国打算在某两个城市之间新增一个航班,预计是在奇奇出发前能够完成。
但现在有M个方案,奇奇想知道,对于每一个方案,奇奇到城市N的幸福度最大能是多少。
奇奇也完全可以选择已有的路线,而不一定要使用新航班
Input
第一行输入整数T,表示T组数据。
每组数据输入两个整数N和L,表示城市个数,已有的航班数量。
接下来一行N个整数,表示每个城市给奇奇带来的幸福度C[i]。
接下来L行,每行三个数字,X, Y, V,表示从城市X飞往城市Y,幸福度V。
再输入一个M,表示有个M种方案。
后面M行,每行三个数字,X, Y, V,表示方案是从城市X飞往城市Y,幸福度V。
不同的航班,如果有同样的X和Y也可能有不同的V。 数据范围:
1 <= T <= 20
1 <= N <= 200000
1 <= L, M <= 100000
1 <= C[i], V <= 1000
1 <= X < Y <= N
Output
对于每组数据,先输出“Case #k:”,k为数据编号,从1开始。
对于每个方案,输出最终奇奇的最大幸福度。
各个方案之间是独立的,没有任何影响。
Sample Input
1
6 3
1 4 5 6 7 1
1 5 10
1 4 3
2 4 100
2
1 6 1000
2 5 110
Sample Output
Case #1:
1002
123
HINT
样例解释:
对于第一个方案,奇奇可以选择直接从1飞到6,只游玩了城市1和城市6,所以幸福度是1000 + 1 + 1 = 1002
对于第二个方案,奇奇的路线是 1 -> 2 -> 5 -> 6,游玩这四个城市,以及坐了一次飞机,幸福度是1 + 4 + 7 + 1 + 110 = 123
用最长路很容易超时…
#include
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
typedef pair pii;
vector V1[N];
vector V2[N];
ll c[N], dp1[N], dp2[N];
int cas, t=0, n, m, q;
void solve1(int id) {
for(int i=0; i
551 (Div. 2)___D dp 或 记忆化搜索 E 模拟
D. Neko and Aki’s Prank
题目大意:
对括号序列的构成过程构造一棵树,对这棵树求最大边独立
解题思路:
可以记忆化搜索,同时贪心,思路是一层一层的加边
那么对于dp的方法,因为是括号序列,每加上一个括号,它的所有连边数(其实说的不准确,自己体会吧)也可以根据以前的状态求出来
对于dp求最大边独立的话,自己画出 n = 3 n=3 n=3 的情况,可以看出最佳情况就是上面所说的一层层加边,对等于括号的数量,即奇数或者偶数加边
代码思路:
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示有 j j j 个左括号、 i i i 个右括号的情况(已修改),那么
转移就只有一行了…
#include
using namespace std;
typedef long long ll;
typedef pair pii;
const int maxn = 3e5 + 10;
const int mod = 1e9 + 7;
int n;
ll dp[1015][1015], vis[1015][1015];
int main() {
scanf("%d", &n);
for (int l=0; l<=n; ++l)
for (int r=l; r<=n; ++r)
dp[l][r] = ((l ? dp[l-1][r] : 0) + dp[l][r-1] + (l+r & 1)) % mod;
printf("%lld", dp[n][n]);
}
552 (Div. 3)___ G(数学)
G - Minimum Possible LCM
题目大意:
n n n 个数 ( 1 e 6 ) (1e6) (1e6),每个数的范围 1 e 7 1e7 1e7,任意两个数可以作 l c m lcm lcm
求 l c m lcm lcm 最小的任意二元组
解题思路:
第一次接触这样的题,神才能想出解题思路啊
还是得硬着头皮说下去的嘛…(其实我也是看了别人的ac代码才看懂,网上也没类似的题解)
现在给你一串数(已排好序):1 2 3 5 6 8 9
如果只能相邻两个数作 l c m lcm lcm,那么答案肯定是 1 和 2 的 l c m lcm lcm,后面的作 l c m lcm lcm 肯定大于前面的(是不是有一点点感觉了?)
那么推广到不相邻呢?
如果还是按照上面那个最小的思路,用只隔一个来看,1 和 3 与 2 和 5 ,成立
如果是 2 和 5 与 3 和 6 ,明显不成立
但是如果拓展到 1 2 3 4 5 6 7 8 9
相邻的情况:1和2是最优
间隔一个:2 和 4 是最优( 2 和 6 也一样)
间隔两个:3 和 6 是最优( 3 和 9 也一样)
又成立了!!!
注意上面的左端点,是递增的,如果都是从 1 开始,结果不成立
那么我们用一个数组记录一个数是否出现过
循环一个 i i i ,找i的倍数,最先出现的两个数,就是当前间隔 i − 1 i-1 i−1 个的最优解
具体不懂可以看代码
时间复杂度: N / 1 + N / 2 + . . . + N / N = N l o g N N/1 + N/2 + ... + N/N = NlogN N/1+N/2+...+N/N=NlogN
N = 1 e 7 N = 1e7 N=1e7(真没想到能刚好卡过)
代码思路:
注意一个数字只需要出现 2 2 2 次就可以了,多的不计
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int N = 1e7 + 5;
vector v[N];
int n, a[maxn], x, y;
int main(){
scanf("%d", &n);
for(int i=1; i<=n; i++) {
scanf("%d", a+i);
if(v[a[i]].size()<2) v[a[i]].push_back(i);
}
ll ans = LONG_LONG_MAX;
for(int i=1; i0){
for(auto k : v[j]){
if(l==-1) l = k;
else if(r==-1) r = k;
else break;
}
if(r!=-1) break;
}
}
if(r==-1) continue;
if(ans > 1LL*a[l]*a[r]/__gcd(a[l],a[r])){
ans = 1LL*a[l]*a[r]/__gcd(a[l],a[r]);
x = l, y = r;
}
}
if(x>y) swap(x, y);
printf("%d %d\n", x, y);
}
Educational CF Round 63 (Div. 2)___D. Beautiful Array (dp)
题目大意:
比最大子串和多给了个 x x x,可以使至多任意一个子串全部 ∗ x * x ∗x ,然后再求最大子串和
代码思路:
因为没有其他操作,在遍历的过程中求最大值就行了
#include
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 10;
ll dp[maxn][3], a[maxn];
int main(){
ll n, x;
scanf("%lld%lld", &n, &x);
for(int i=1; i<=n; i++) scanf("%lld", a+i);
ll ans = 0;
for(int i=1; i<=n; i++){
dp[i][0] = max(0ll, dp[i-1][0] + a[i]);
dp[i][1] = max(0ll, max(dp[i-1][0], dp[i-1][1]) + (a[i] * x));
dp[i][2] = max(0ll, max(dp[i-1][1], dp[i-1][2]) + a[i]);
ans = max(ans, *max_element(dp[i], dp[i] + 3));
}
printf("%lld\n", ans);
}
Comet OJ - Contest #3 子序列子序列子序列…
解题思路:
对于子序列 a 1 + a 2 + . . . + a k a_1 + a_2 + ... + a_k a1+a2+...+ak 是完美序列,那么 2 k − 1 2^{k-1} 2k−1 ∗ ( a 1 + a 2 + . . . + a k ) * (a_1 + a_2 + ... + a_k) ∗(a1+a2+...+ak) 是 m m m 的倍数
设 m = m 0 ∗ 2 c m = m_0 * 2^c m=m0∗2c , m 0 m_0 m0 为奇数,则分类讨论如下:
若 j ≤ c j ≤ c j≤c,则 d p [ j ] [ k ] dp[j][k] dp[j][k] 表示长度为 j j j 的序列,求和后对 m / 2 j − 1 m/2^{j-1} m/2j−1 取模得k的子序列个数
若 j > c j>c j>c,则 d p [ c + 1 ] [ k ] dp[c+1][k] dp[c+1][k] 表示长度至少为 c + 1 c+1 c+1 的序列,求和后对 m / 2 c m/2^c m/2c 取模得 k k k 的子序列个数
对于子序列是否包含 a i ai ai ,递推求出 i = 1 i=1 i=1 ~ n n n 的所有情况
最后复杂度为 O ( n m ) O(nm) O(nm)
#include
#define rint register short int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair pii;
const ll mod = 1e9 + 7;
const int maxn = 5e3 + 10;
int a[maxn], b[maxn], dp[15][maxn];
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++) scanf("%d", a+i);
int c = 0, ans = 0;
for(int i=m; i%2==0; i/=2, c++);
for(rint t=1; t<=c+1; t++) { // 枚举前 c位
memset(dp,0,sizeof(dp));
int d = m / (1 << t-1);
dp[0][0] = 1;
for(rint i=1; i<=n; i++) { // 逐个插入 a[i]
for(rint k=0; k=0; j--) {
for(rint k=0; k c 的情况
for(rint k=0; k
—— LCS ——
LCS + 输出最长公共子序列
带输出:
#include
using namespace std;
typedef long long ll;
char a[1005], b[1005];
int dp[1005][1005];
void lcs(char a[], char b[]){
int len1 = strlen(a);
int len2 = strlen(b);
memset(dp, 0, sizeof(dp));
for(int i=1; i<=len1; i++)
for(int j=1; j<=len2; j++){
if(a[i-1]==b[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
printf("%d\n", dp[len1][len2]);
}
void print(char a[], char b[]){
stack s;
int len1 = strlen(a);
int len2 = strlen(b);
while(dp[len1][len2]){
if(dp[len1][len2] == dp[len1-1][len2]) len1--;
else if(dp[len1][len2] == dp[len1][len2-1]) len2--;
else {
len1--, len2--;
s.push(a[len1]);
}
}
while(!s.empty()){
printf("%c", s.top());
s.pop();
}
puts("");
}
int main(){
while(~scanf("%s %s", a, b)){
lcs(a, b);
print(a, b);
}
}
LCS + 输出最长公共子串
带输出:
#include
using namespace std;
typedef long long ll;
const int N = 1005;
char a[N], b[N];
int dp[N][N], index, res;
void lcs(char a[], char b[]){
int len1 = strlen(a);
int len2 = strlen(b);
index = res = 0;
memset(dp, 0, sizeof(dp));
for(int i=1; i<=len1; i++)
for(int j=1; j<=len2; j++){
if(a[i-1]==b[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
if(res <= dp[i][j]){
res = dp[i][j];
index = i;
}
}
printf("%d\n", res);
}
void print(char a[], char b[]){
for(int i=index-res; i
LIS
最长上升子序列模板:
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef unsigned long long ll;
typedef pair pii;
const ll mod = 1e9 + 7;
const int maxn = 1e5 + 10;
int n, a[maxn], t[maxn], ans;
vector ls;
int lowbit(int x) {
return x&(-x);
}
void update(int x, int p){
for(int i=x; i<=ls.size(); i+=lowbit(i))
t[i] = max(t[i], p);
}
int query(int x){
int ret = 0;
for(int i=x; i; i-=lowbit(i))
ret = max(ret, t[i]);
return ret;
}
int getid(int x){
return lower_bound(ls.begin(), ls.end(), x) - ls.begin() + 1;
}
int main() {
scanf("%d", &n);
for(int i=1; i<=n; i++) {
scanf("%d", a+i);
ls.push_back(a[i]);
}
sort(ls.begin(), ls.end());
ls.erase(unique(ls.begin(), ls.end()), ls.end());
for(int i=1; i<=n; i++){
int p = getid(a[i]);
p = query(p-1) + 1;
ans = max(ans, p);
update(getid(a[i]), p);
}
printf("%d\n", ans);
}
UESTC-1425___Another LCIS —— 线段树 + 最长连续上升子序列
题目大意:
线段树维护 最长连续上升子序列,注意是连续
代码思路:
t t t —— 该区间的 最长连续上升子序列
l n u m lnum lnum—— 左子树的左端点
r n u m rnum rnum —— 右子树的右端点
l s u m lsum lsum —— 左子树以左端点为起点的 最长连续上升子序列
r s u m rsum rsum —— 右子树以右端点为终点的 最长连续上升子序列
解题思路:
左右端点很好维护,维护上升子序列的时候,就要判断左右子树的拼接问题,用端点值进行比较,就可以判断是否可以连接起来,所以需要记录端点
最后在查询的时候,因为左右子树也没有拼接,所以也不能直接判断,和上面也需要进行端点值比较,但是必须要注意一点,就是如果能拼接,还要注意是不是在查询的区间内,对子序列长度所在区间和查询区间进行对比,筛选,不然gg
核心:线段树维护 最长连续上升子序列 get!
#include
using namespace std;
typedef long long ll;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int maxn = 100000+10;
int t[maxn<<2],lazy[maxn<<2];
int lnum[maxn<<2], rnum[maxn<<2];
int lsum[maxn<<2], rsum[maxn<<2];
void pushup(int l,int r,int rt) {
lnum[rt] = lnum[rt<<1];
rnum[rt] = rnum[rt<<1|1];
lsum[rt] = lsum[rt<<1];
rsum[rt] = rsum[rt<<1|1];
t[rt] = max(t[rt<<1],t[rt<<1|1]);
int leftright = rnum[rt<<1]; ///左区间的右侧数字
int rightleft = lnum[rt<<1|1]; ///右区间的左侧数字
int mid = (l+r)>>1;
///两个区间可能拼接在一起。
if(leftright>1;
build(lson);
build(rson);
pushup(l,r,rt);
}
void pushdown(int rt) {
if(lazy[rt]) {
lazy[rt<<1] += lazy[rt];
lazy[rt<<1|1] += lazy[rt];
lnum[rt<<1] += lazy[rt];
rnum[rt<<1] += lazy[rt];
lnum[rt<<1|1] += lazy[rt];
rnum[rt<<1|1] += lazy[rt];
lazy[rt] = 0;
}
}
void update(int L,int R,int C,int l,int r,int rt) {
if(L<=l&&r<=R) {
lnum[rt] += C;
rnum[rt] += C;
lazy[rt] += C;
return ;
}
int m = (l+r)>>1;
pushdown(rt);
if(L<=m) update(L,R,C,lson);
if(R>m) update(L,R,C,rson);
pushup(l,r,rt);
}
int query(int L,int R,int l,int r,int rt) {
if(L<=l&&r<=R) return t[rt];
int m = (l+r)>>1;
pushdown(rt);
int ans = 1;
if(L<=m) ans = max(ans,query(L,R,lson));
if(R>m) ans = max(ans,query(L,R,rson));
if(rnum[rt<<1] < lnum[rt<<1|1]) {
int rans = min(R,m+lsum[rt<<1|1]);
int lans = max(L,m-rsum[rt<<1]+1);
ans = max(ans,rans-lans+1); // 注意查询区间的限制
}
return ans;
}
int main() {
int T, n, q, Case=0;
char str[10];
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &q);
build(1,n,1);
int l, r, c;
printf("Case #%d:\n", ++Case);
while(q--) {
scanf("%s", &str);
if(str[0]=='q') {
scanf("%d %d", &l, &r);
printf("%d\n", query(l,r,1,n,1));
}
if(str[0]=='a') {
scanf("%d %d %d", &l, &r, &c);
update(l,r,c,1,n,1);
}
}
}
}
——背包DP——
01背包、完全背包、多重背包+优化 模板
01背包是每种只有一件,完全背包是每种无限件,而多重背包是每种有限件。
01背包:
#include
using namespace std;
typedef long long ll;
const int maxn=10005;
int n, m;
int w[maxn], v[maxn];
int dp[maxn];
bool path[maxn][maxn];
void output() { //路径输出
int i=n, j=m;
while (i>=0 && j>0) {
if(path[i][j] == 1) {
printf("%d ", i+1);
j -= w[i];
}
i--;
}
printf("\n");
}
int main() {
scanf("%d%d", &n, &m);
for(int i=0; i=w[i]; j--) //注意是逆序、逆序、逆序!!
if (dp[j] < dp[j - w[i]] + v[i]) {
dp[j] = dp[j - w[i]] + v[i];
path[i][j]=true;
}
}
printf("%d\n", dp[m]);
output();
}
完全背包:
#include
using namespace std;
typedef long long ll;
const int maxn=10005;
int n, m;
int w[maxn], v[maxn];
int dp[maxn];
bool path[maxn][maxn];
void output() {
int i=n, j=m;
while (i>=0 && j>0) {
if(path[i][j] == 1) {
printf("%d ", i+1);
j -= w[i];
} else i--;
}
printf("\n");
}
int main() {
scanf("%d%d", &n, &m);
for(int i=0; i
多重背包:
#include
using namespace std;
typedef long long ll;
const int maxn=10005;
int n, m;
int w[maxn], v[maxn], c[maxn];
int dp[maxn];
bool path[maxn][maxn];
void output() {
int i=n, j=m;
while (i>=0 && j>0) {
if(path[i][j] == 1 && c[i]) {
printf("%d ", i+1);
j -= w[i];
c[i]--;
} else
i--;
}
printf("\n");
}
int main() {
scanf("%d%d", &n, &m);
for(int i=0; i=w[i]; j--) //也是逆序!! 相当于重复01背包!
if (dp[j] < dp[j - w[i]] + v[i]) {
dp[j] = dp[j - w[i]] + v[i];
path[i][j] = true;
}
printf("%d\n", dp[m]);
output();
}
多重背包+二进制优化:
#include
using namespace std;
typedef long long ll;
const int maxn=100005;
int n, m;
int w[maxn], v[maxn], c[maxn];
int dp[maxn];
void zero(int weight, int value) {
for(int i=m; i>=weight; i--)
dp[i] = max(dp[i], dp[i-weight]+value);
}
void complet(int weight, int value) {
for(int i=weight; i<=m; i++)
dp[i] = max(dp[i], dp[i-weight]+value);
}
void multi(int weight, int value, int amount) {
if(weight*amount >= m) { //容量小的时候相当于无限背包
complet(weight, value);
return;
}
int k = 1;
while(k < amount) { //当k个仍小于容量时,用01背包
zero(k*weight, k*value);
amount -= k;
k <<= 1;
}
zero(amount*weight, amount*value); //最后剩余再用01背包
}
int main() {
scanf("%d%d", &n, &m);
memset(dp, 0, sizeof(dp));
for(int i=0; i
超大背包
一:超大体积
题意: n ≤ 500 n≤500 n≤500 个物品,所有物品的价值总和 v ≤ 5000 v≤5000 v≤5000,体积 w ≤ 1 0 10 w≤10^{10} w≤1010,容量为 S ≤ 1 0 9 S≤10^{9} S≤109
方法: d p [ i ] dp[i] dp[i] 表示价值为 i i i 时的最小重量
转移方程: d p [ i ] = m i n ( d p [ i ] , d p [ i − v [ j ] ] + w [ j ] ) dp[i] = min(dp[i],dp[i-v[j]] + w[j]) dp[i]=min(dp[i],dp[i−v[j]]+w[j])
时间复杂度: O ( n v ) O(nv) O(nv)
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
//#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
typedef long long ll;
//using pii = pair ;
const int maxn = 1e5 + 10;
const int inf = INT_MAX;
int T, n, B, V;
ll dp[maxn];
int w[maxn], v[maxn];
int main() {
scanf("%d", &T);
while(T--){
V = dp[0] = 0;
scanf("%d%d", &n, &B);
for(int i=1; i<=n; i++){
scanf("%d%d", w+i, v+i);
V += v[i];
}
for(int i=1; i<=V; i++) dp[i] = inf;
for(int i=1; i<=n; i++)
for(int j=V; j>=v[i]; j--)
dp[j] = min(dp[j], dp[j-v[i]] + w[i]);
for(int i=V; ~i; i--)
if(dp[i] <= B){
printf("%d\n", i);
break;
}
}
}
二:超大价值
题意: n ≤ 1000 n≤1000 n≤1000 个物品,每个物品的价值 v ≤ 1 0 9 v≤10^{9} v≤109,体积 w ≤ 1000 w≤1000 w≤1000,容量为 S ≤ 1 0 9 S≤10^{9} S≤109
设 f ( x ) f(x) f(x) 为体积为 x x x 时的最优解
体积和 V ≤ 1 e 6 V≤1e6 V≤1e6,考虑把一个大背包分成两个小背包 x x x 和 S − x S-x S−x
但并不是所有的x都是正确的,因为两个小背包都有可能有空隙,空隙是可以转移的
但是我们要缩小x的范围,即缩小答案范围
满足 ∣ x − ( S − x ) ∣ < = m a x w |x-(S-x)|<=maxw ∣x−(S−x)∣<=maxw 的 x x x 的最优解 一定是合法的
若当前 x x x 和 S − x S-x S−x 是合法的,那么这两个背包里的物品是可以互相转移的
所以一直转移到容量差值小于等于 m a x w maxw maxw (仔细想想,有点东西)
因此 S 2 − m a x w 2 ≤ x ≤ S 2 + m a x w 2 \frac{S}{2}-\frac{maxw}{2}≤x≤\frac{S}{2}+\frac{maxw}{2} 2S−2maxw≤x≤2S+2maxw 这部分内
一定有 f ( x ) + f ( S − x ) = f ( S ) f(x) + f(S-x) = f(S) f(x)+f(S−x)=f(S) 的最优解
所以只需要枚举 S 2 − m a x w 2 ≤ x ≤ S 2 + m a x w 2 \frac{S}{2}-\frac{maxw}{2}≤x≤\frac{S}{2}+\frac{maxw}{2} 2S−2maxw≤x≤2S+2maxw 这个区间里的每个 x x x 即可
对这个区间的 x x x 继续往下分,发现新的总区间为:
S 4 − 3 ∗ m a x w 4 ≤ x ≤ S 4 + 3 ∗ m a x w 4 \frac{S}{4}-\frac{3*maxw}{4}≤x≤\frac{S}{4}+\frac{3*maxw}{4} 4S−43∗maxw≤x≤4S+43∗maxw
当 S S S 一直被分到 S 2 k \frac{S}{2^k} 2kS 时,总区间一定是:
S 2 k − m a x w ≤ x ≤ S 2 k + m a x w \frac{S}{2^k}-maxw≤x≤\frac{S}{2^k}+maxw 2kS−maxw≤x≤2kS+maxw 的子集
那么只需要预处理 S 2 k − m a x w ≤ x ≤ S 2 k + m a x w \frac{S}{2^k}-maxw≤x≤\frac{S}{2^k}+maxw 2kS−maxw≤x≤2kS+maxw 范围的背包
一层一层递归回去即可
记 l o w [ i ] low[i] low[i] 为第 i i i 层的左边界,对于 l o w [ i ] ≤ x ≤ l o w [ i ] + 2 ∗ m a x w low[i]≤x≤low[i]+2*maxw low[i]≤x≤low[i]+2∗maxw 的每个 x x x 的答案
一定在 [ x − m a x w 2 , x 2 ] [\frac{x-maxw}{2},\frac{x}{2}] [2x−maxw,2x] 里
时间复杂度: O ( w 2 l o g S ) O(w^2 logS) O(w2logS)
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
//#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
typedef long long ll;
using pii = pair ;
const int maxn = 1e3 + 10;
int n, S, maxw, cnt;
int w[maxn], v[maxn];
ll dp[maxn*5], low[maxn];
ll f[40][maxn*5];
int main() {
scanf("%d%d", &n, &S);
for(int i=1; i<=n; i++){
scanf("%d%d", w+i, v+i);
maxw = max(maxw, w[i]);
}
for(int i=1; i<=n; i++)
for(int j=w[i]; j<=maxw*4; j++)
dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
while(S > 0){
low[++cnt] = S;
S = S - maxw >> 1;
}
for(int i=cnt; i; i--)
for(int j=low[i]; j<=low[i]+2*maxw; j++){
if(j <= maxw*4) f[i][j-low[i]] = dp[j];
else for(int k=(j-maxw)/2; k*2<=j; k++)
f[i][j-low[i]] = max(f[i][j-low[i]], f[i+1][k-low[i+1]] + f[i+1][j-k-low[i+1]]);
}
printf("%lld\n", f[1][0]);
}
三:超大体积 + 超大价值
题意: n ≤ 40 n≤40 n≤40 个物品,每个物品的价值 v ≤ 1 0 15 v≤10^{15} v≤1015,体积 w ≤ 1 0 15 w≤10^{15} w≤1015,容量为 S ≤ 1 0 18 S≤10^{18} S≤1018
折半搜索
处理出前 20 20 20 个物品的所有情况
再枚举后 20 20 20 个物品的所有情况时,二分找即可
注意对重量排序后去重的优化,在某些情况不可用
时间复杂度: O ( 2 n 2 l o g n ) O(2^{\frac{n}{2}} logn) O(22nlogn)
P S : PS: PS:这道模板题因为体重和价值一样,即使固定 S S S,排序去重也是 O K OK OK 的
#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
using pll = pair <ll,ll>;
const int maxn = 40;
int N, n;
ll w[maxn], v[maxn], S, ans, ansbit;
pair <ll, pll > pi[1ll<<(maxn/2)], tmp;
int main() {
scanf("%d%lld", &N, &S);
for(int i=0; i<N; i++) scanf("%lld%lld", w+i, v+i);
n = N >> 1;
for(int i=0; i<(1<<n); i++) {
ll sumw = 0, sumv = 0;
for(int j=0; j<n; j++)
if(i >> j & 1) {
sumw += w[j];
sumv += v[j];
}
pi[i] = make_pair(sumw, pll(sumv, i) );
}
sort(pi, pi+(1<<n));
int cnt = 1;
for(int i=1; i<(1<<n); i++)
if(pi[cnt-1].second.first < pi[i].second.first)
pi[cnt++] = pi[i];
ans = ansbit = 0;
for(int i=0; i<(1<<(N-n)); i++) {
ll sumw = 0, sumv = 0;
for(int j=0; j<(N-n); j++)
if(i >> j & 1) {
sumw += w[j+n];
sumv += v[j+n];
}
if(sumw > S) continue;
tmp = make_pair(S-sumw, pll(9e18, 0));
int id = lower_bound(pi, pi+cnt, tmp) - pi - 1;
if(sumw + pi[id].first > S) continue;
if(sumv + pi[id].second.first < ans) continue;
ans = max(ans, sumv + pi[id].second.first);
ansbit = pi[id].second.second | ((ll)i<<n);
}
for(int i=0; i<N; i++)
printf("%d", (ansbit & (1ll<<i)) ? 1 : 0);
}
POJ-3093___Margaritas on the River Walk——01背包的变异
题目大意:
多组样例,在这里我们假设有 n n n个物品,容量为 m m m的背包,问有多少种方案,使得剩下的所有物品都装不进背包。。。。。。
解题思路:
假如在剩下的物品中,体积最小为 w w w的物品装不进背包,那么很明显所有背包中体积小于 w w w的都被放进去了,依此思路,我们给所有背包排个序,然后依次枚举每个背包,将这个背包当做剩下的体积最小的背包,所以,当枚举到第 i i i个背包时,第 1... i − 1 1...i-1 1...i−1件背包都被放进去了。
设第 i i i个背包的体积为 w [ i ] w[i] w[i],前 i i i个物品的体积和为 s u m [ i ] sum[i ] sum[i]
当枚举到 i i i时,由于第 1... i − 1 1...i-1 1...i−1件物品已被放入背包,所以背包剩余容量为 m − s u m [ i − 1 ] m-sum[i-1] m−sum[i−1],这时,要使得第 i i i件物品放不进去,就要用第 i + 1... n i+1...n i+1...n的物品装满剩余容积为 m − s u m [ i − 1 ] − w [ i ] + 1 m-sum[i-1]-w[i]+1 m−sum[i−1]−w[i]+1 ~ m − s u m [ i − 1 ] m-sum[i-1] m−sum[i−1]的背包,这样使得第 i i i件物品恰好放不进背包(背包剩余容量小于 w [ i ] w[i] w[i])。
加下来考虑枚举问题。
① ① ①从 1 1 1顺序枚举到 n n n,则第一次对 ( 2 , n ) (2,n) (2,n)做 01 01 01背包,第二次对 ( 3 , n ) (3,n) (3,n)做 01 01 01背包 . . . . . . ...... ......时间复杂度为 O ( n 2 m ) O(n^2m) O(n2m)
② ② ②从 n n n逆序枚举到 1 1 1,则我们需要对 ( i + 1 , n ) (i+1,n) (i+1,n)做 01 01 01背包,对 ( i , n ) (i,n) (i,n)做 01 01 01背包,对 ( i − 1 , n ) (i-1,n) (i−1,n)做 01 01 01背包,会发现,每次做 01 01 01背包都是放入了 1 1 1个物品,显然我们可以对此进行优化,在上一次做完背包之后,把第 i i i件物品放入上一次做完的背包,就得到了我们需要的状态,这样从头到尾只做了一次背包。时间复杂度为 O ( n m ) O(nm) O(nm)
代码思路:
用 s u m [ i ] sum[i] sum[i]存前 i i i件物品的和,每次枚举物品的时候,要注意 m − s u m [ i − 1 ] − w [ i ] + 1 m-sum[i-1]-w[i]+1 m−sum[i−1]−w[i]+1小于 0 0 0的情况,这时用 m a x ( m − s u m [ i − 1 ] − w [ i ] + 1 , 0 ) max(m-sum[i-1]-w[i]+1, 0) max(m−sum[i−1]−w[i]+1,0)
注意,每次枚举的过程中,我们要先将第 i + 1... n i+1...n i+1...n的物品装满剩余容积为 m − s u m [ i − 1 ] − w [ i ] + 1 m-sum[i-1]-w[i]+1 m−sum[i−1]−w[i]+1 ~ m − s u m [ i − 1 ] m-sum[i-1] m−sum[i−1]的结果加入到 a n s ans ans 中,然后再将当前物品加入到背包中
核心:灵活运用背包顺序与逆序的巧妙思路,最重要的是明白背包dp的本质,本题就运用这个思路逆序更新背包,实在巧妙
#include
using namespace std;
typedef long long ll;
int n, m, ans;
int dp[1005], w[35], sum[35];
int main() {
int cas, n, m, t=0;
scanf("%d", &cas);
while(cas--) {
sum[0]=ans=0;
memset(dp, 0, sizeof(dp));
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++)
scanf("%d", &w[i]);
sort(w+1, w+n+1);
for(int i=1; i<=n; i++)
sum[i]=sum[i-1]+w[i];
dp[0]=1;
for(int i=n; i>=1; i--) {
int k = max(m-sum[i-1]-w[i]+1, 0);
for(int j=m-sum[i-1]; j>=k; j--)
ans += dp[j];
for(int j=m; j>=w[i]; j--) //将第i个物品放入背包,即重新编排了一次背包
dp[j] += dp[j-w[i]];
}
if(m
CodeForces - 987C___Three displays——动态规划(背包+思维)
题目大意:
n n n个物品,每个物品体积为 s i s_i si,花费为 c i c_i ci,每次选 3 3 3个物品,使这三个物品 i < j < k ii<j<k,并且 s i < s j < s k s_isi<sj<sk,求最小花费???
解题思路:
明显是dp,问题是怎么dp,在这里给出两种方法,因为要选三个物品,下面一种是特殊的方法,一种是一般的方法:
① ① ①: f [ i , k ] f[i,k] f[i,k] 代表第 i i i个物品作为选出的三个物品中的第 k k k个物品的最小花费,那么只需要遍历一遍 i i i,每次遍历取两个物品,在前i个物品中选取符合要求且花费最小的物品,那么状态转移方程就是: f [ i , k ] = m i n ( f [ j , k − 1 ] + c [ i ] ) f[i,k]=min(f[j,k−1]+c[i]) f[i,k]=min(f[j,k−1]+c[i]) ,其中: j < i jj<i且 s [ j ] < s [ i ] s[j]s[j]<s[i]。
② ② ②:因为只需要选三个物品,那么我先遍历一遍i,每次遍历中,找到i之后花费最小的那个物品,这样就选择了二个物品的最优解,记录到这个 d p [ i ] dp[i] dp[i]中
再次遍历一遍i,每次遍历选择i之前花费最小的那个物品,这样既不会重复,也就选到了三个物品的最优解!
PS:第一种方法可以延伸到任意数量的物品(复杂度允许的话),而第二种方法只能选择三个物品,因为只能遍历两次~
代码思路:
状态转移: f [ i , k ] = m i n ( f [ j , k − 1 ] + c [ i ] ) f[i,k]=min(f[j,k−1]+c[i]) f[i,k]=min(f[j,k−1]+c[i]) ,其中: j < i jj<i且 s [ j ] < s [ i ] s[j]s[j]<s[i]。
时间复杂度: O ( N 2 ) O(N^2) O(N2)
核心:熟练的使用dp,不行就多练吧弟弟
第一种方法(推荐):
#include
using namespace std;
const int maxn = 3007, inf = 0x3f3f3f3f;
int f[maxn][4], n, val[maxn], cost[maxn], ans = inf;
int main()
{
scanf("%d", &n);
for (int i=1; i<=n; i++) scanf("%d", val + i);
for (int i=1; i<=n; i++) scanf("%d", cost + i);
memset(f, 0x3f, sizeof(f));
for (int i=1; i<=n; i++) {
f[i][1] = cost[i];
for (int k=2; k<=3; k++)
for (int j=1; j
——数位DP——
浅谈数位DP
在了解数位dp之前,先来看一个问题:
例1.求a~b中不包含49的数的个数. 0 < a、b < 2*10^9
注意到n的数据范围非常大,暴力求解是不可能的,考虑dp,如果直接记录下数字,数组会开不起,该怎么办呢?要用到数位dp.
数位dp一般应用于:
求出在给定区间[A,B]内,符合条件P(i)的数i的个数.
条件P(i)一般与数的大小无关,而与 数的组成 有关.
这样,我们就要考虑一些特殊的记录方法来做这道题.一般来说,要保存给定数的每个位置的数.然后要记录的状态为当前操作数的位数,剩下的就是根据题目的需要来记录.可以发现,数位dp的题做法一般都差不多,只是定义状态的不同罢了.
下面开始针对例题进行分析:
我们要求[a,b]不包含49的数的个数,可以想到利用前缀和来做,具体来说,就是[a,b] = [0,b] - [0,a),(")"是不包括a),我们先求出给定a,b的每个位置的数,保存在数组s中,例如a = 109,那么a[1] = 9,a[2] = 0,a[3] = 1.然后开始dp,我们可以选择记忆化搜索或者是递推,前一种相对于第二种而言简单和较为容易理解一些,所以我们选择记忆化搜索.那么需要记录些什么呢?首先长度是一定要记录的,然后记录当前的数位是否为4,这样就便于在记忆化搜索中得到答案.
然后进行记忆化搜索,记录上一位是否为4和枚举这一位,如果没有限制的话很好办,直接枚举就可以了,但是这样可能会超空间,因此我们每次都必须要判断是否有最大的限制,这里不是很好说,看代码更容易理解:
#include
using namespace std;
int a, b, shu[20], dp[20][2];
int dfs(int len, bool if4, bool shangxian) {
if (len == 0)
return 1;
if (!shangxian && dp[len][if4])
return dp[len][if4];
int cnt = 0, maxx = (shangxian ? shu[len] : 9);
for (int i = 0; i <= maxx; i++) {
if (if4 && i == 9)
continue;
cnt += dfs(len - 1, i == 4, shangxian && i == maxx);
}
return shangxian ? cnt : dp[len][if4] = cnt;
}
int solve(int x) {
memset(shu, 0, sizeof(shu));
int k = 0;
while (x) {
shu[++k] = x % 10;
x /= 10;
}
return dfs(k, false, true);
}
int main() {
scanf("%d%d", &a, &b);
printf("%d\n", solve(b) - solve(a - 1));
}
再来看一道题:例题2.求a~b中不包含62和4的数的个数. 0 < a、b < 2*10^9
分析:和上一题一样,只需要再判断一下4是否出现和上一位是否为6即可.
#include
using namespace std;
int a, b,shu[20],dp[20][2];
int dfs(int len, bool if6, bool shangxian) {
if (len == 0)
return 1;
if (!shangxian && dp[len][if6])
return dp[len][if6];
int cnt = 0, maxx = (shangxian ? shu[len] : 9);
for (int i = 0; i <= maxx; i++) {
if (i == 4 || if6 && i == 2)
continue;
cnt += dfs(len - 1, i == 6, shangxian && i == maxx);
}
return shangxian ? cnt : dp[len][if6] = cnt;
}
int solve(int x) {
memset(shu, 0, sizeof(shu));
int k = 0;
while (x) {
shu[++k] = x % 10;
x /= 10;
}
return dfs(k, false, true);
}
int main() {
scanf("%d%d", &a, &b);
printf("%d\n", solve(b) - solve(a - 1));
}
例题6:POJ3252 Round Numbers
题目大意:求区间[a,b]中的数转化为二进制后0比1多的数的个数.
分析:典型的数位dp题,先在二进制上做dp,最后转化到十进制上.求出[0,b]和[0,a-1]的答案,相减就可以了.
一个坑点:二进制数必须要存在,也就是说必须要有一个1开头.(can的来源)
#include
using namespace std;
typedef long long ll;
ll a, b, num[40], cnt, f[100][100][100];
ll dfs(ll len, ll num1, ll num0, bool limit,bool can) {
if (len == 0) {
if (num0 >= num1)
return 1;
return 0;
}
if (!limit && f[len][num1][num0])
return f[len][num1][num0];
ll cntt = 0, shangxian = (limit ? num[len] : 1);
for (int i = 0; i <= shangxian; i++) {
if (i == 0) {
if (can)
cntt += dfs(len - 1, num1, num0 + 1, (limit && i == shangxian), can || i == 1);
else
cntt += dfs(len - 1, num1, num0, (limit && i == shangxian), can || i == 1);
}
if (i == 1)
cntt += dfs(len - 1, num1 + 1, num0, (limit && i == shangxian),can || i == 1);
}
if (!limit)
return f[len][num1][num0] = cntt;
else
return cntt;
}
ll solve(ll x) {
memset(num, 0, sizeof(num));
cnt = 0;
while (x) {
num[++cnt] = x % 2;
x /= 2;
}
return dfs(cnt, 0, 0, true,false);
}
int main() {
scanf("%lld%lld", &a, &b);
printf("%lld\n", solve(b) - solve(a - 1));
}
经过对以上例题的探讨与研究,自然也就不难得到数位dp模板(…根据实际情况来填):
#include
using namespace std;
int t;
long long dp[19][19][2005];
long long l, r;
int shu[20];
long long dfs(int len,..., bool shangxian) {
if (len == 0)
return ...;
if (!shangxian && dp[len][...])
return dp[len][...]; //dp数组的内容应和dfs调用参数的内容相同,除了是否达到上限
long long cnt = 0;
int maxx = (shangxian ? shu[len] : 9);
for (int i = 0; i <= maxx; i++) {
...;
cnt += dfs(len - 1,..., shangxian && i == maxx);
}
if (!shangxian)
dp[len][...] = cnt;
return cnt;
}
long long solve(long long x) {
int k = 0;
while (x) {
shu[++k] = x % 10;
x /= 10;
}
return dfs(k,...,1)
}
int main() {
memset(dp, 0, sizeof(dp));
scanf("%lld%lld", &l, &r); //有些题目其实并不需要用到long long
printf("%lld\n", solve(r) - solve(l - 1)); //只有满足区间减法才能用
}
牛客第七场 H Pair —— 数位dp
解题思路:
考虑到高位决定结果,所以从高位开始数位dp
o a oa oa 与 o b ob ob 表示 a a a 和 b b b 是否到达了上界
o a n d oand oand 与 o x o r oxor oxor 表示高位的和与异或是否出现非零
z a za za 与 z b zb zb 限制正数
核心:两个数同时数位dp
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair pii;
const ll mod = 998244353;
const int maxn = 2e6 + 10;
int T, A, B, C;
int a[40], b[40], c[40];
ll dp[40][2][2][3][3][2][2];
ll dfs(int wz, int oa, int ob, int oand, int oxor, int za, int zb){
ll& ret = dp[wz][oa][ob][oand+1][oxor+1][za][zb];
if(ret != -1) return ret;
if(wz == 0){
if((oand>0 || oxor<0) && za && zb) return ret = 1;
return ret = 0;
}
wz--, ret = 0;
for(int i=0; i<=(oa?1:a[wz]); i++)
for(int j=0; j<=(ob?1:b[wz]); j++)
ret += dfs(wz, oa|(i^a[wz]), ob|(j^b[wz]), oand?oand:(i&j)-c[wz],\
oxor?oxor:(i^j)-c[wz], za|i, zb|j);
return ret;
}
int main() {
scanf("%d", &T);
while(T--){
scanf("%d%d%d", &A, &B, &C);
memset(dp, -1, sizeof(dp));
for(int i=30; ~i; i--){
a[i] = (A >> i) & 1;
b[i] = (B >> i) & 1;
c[i] = (C >> i) & 1;
}
printf("%lld\n", dfs(31,0,0,0,0,0,0));
}
}
——状压DP——
洛谷___P2622 关灯问题II + P3959 宝藏 —— 状压dp
题目大意:
现有 n n n 盏灯,以及 m m m 个按钮。每个按钮可以同时控制这 n n n 盏灯—— 按下了第 i i i 个按钮,对于所有的灯都有一个效果。按下i按钮对于第 j j j 盏灯,是下面 3 种效果之一:如果 a [ i ] [ j ] a[i][j] a[i][j] 为 1 1 1,那么当这盏灯开了的时候,把它关上,否则不管;如果为 − 1 -1 −1的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是 0 0 0,无论这灯是否开,都不管。
现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。
#include
using namespace std;
typedef long long ll;
typedef pair pii;
const ll mod = 1e9 + 7;
const int maxn = 3e5 + 10;
int n, m, a[105][1005];
int vis[1<<15];
struct Node{
int s, step;
Node(){}
Node(int S, int Step){
s = S, step = Step;
}
} q;
int bfs(){
queue Q;
Q.push(Node((1<
题目大意:
一张图,有 n n n 个宝藏和 m m m 条路,初始可任选一个宝藏点,开凿新的宝藏点,需要的代价为 L × K L×K L×K
L L L 代表这条道路的长度, K K K 代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)
问找到所有宝藏的最小代价
核心:状压dp模板
#include
using namespace std;
typedef long long ll;
typedef pair pii;
const ll mod = 1e9 + 7;
const int maxn = 3e5 + 10;
int n, m, mp[15][15];
int num[15], dp[1<<15];
int ans = INT_MAX;
void dfs(int s) {
for(int i=1; i<=n; i++) { // 选择第一个点
if(s & (1< dp[s] + num[i]*mp[i][j]) {
num[j] = num[i] + 1;
dp[s|(1<
牛客第五场 E independent set 1 —— 状压 dp
题目大意:
n n n 个点,求所有能组成的集的最大独立集的和
解题思路:
n ≤ 26 n≤26 n≤26,所以可以 状压 d p dp dp
对于枚举的 x x x , x x x 的最后一个 1 1 1 一定是最后改变的
a x a_x ax 表示 x x x 这个点连的边的状态
l b x lb_x lbx 表示 x x x 二进制中最低位为 1 1 1 的位置
__ b u i l t i n builtin builtin_ c t z ( x ) ctz(x) ctz(x) 表示 x x x 末尾有多少个 0 0 0
d p [ x ] dp[x] dp[x] 表示 x x x 这个集的最大独立集的 s i z e size size
d p [ x ] = m a x ( d p [ x − ( 1 < < l b x ) ] , d p [ x dp[x] = max(dp[x - (1<dp[x]=max(dp[x−(1<<lbx)],dp[x & ( ( ( ~__ b u i l t i n builtin builtin_ c t z ( x ) ) ] + 1 ) ctz(x))] + 1) ctz(x))]+1)
P S : PS: PS:题目卡内存。。用 4 4 4 字节的 i n t int int 会爆
需要用 1 1 1 字节的 c h a r char char ,最多只能存 255 > n 255 > n 255>n
核心:位元状压dp与独立集的应用
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
//#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
typedef long long ll;
using pii = pair ;
int n, m, a[26];
ll ans;
char dp[(1<<26) + 5];
int lowbit(int x){
return x & (-x);
}
int main() {
scanf("%d%d", &n, &m);
while(m--){
int u, v;
scanf("%d%d", &u, &v);
a[u] |= (1<
——树形DP——
树的直径:
从任意一点出发找到最远点 p o s pos pos
再从 p o s pos pos 出发找到新的最远点
p o s pos pos 和新的最远点的路径就是直径
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair pii;
const int maxn = 1e5 + 5;
int ans, pos;
vector g[maxn];
void dfs(int u, int dis, int fa){
if(dis > ans) pos = u, ans = dis;
for(auto v : g[u]){
if(v.first == fa) continue;
dfs(v.first, dis+v.second, u);
}
}
int main() {
int u, v, w;
while(~scanf("%d%d%d", &u, &v, &w)){
g[u].push_back({v, w});
g[v].push_back({u, w});
}
dfs(1, 0, 0);
ans = 0;
dfs(pos, 0, 0);
printf("%lld\n", ans);
}
例题:
题目大意:
求树上多个人汇聚到一点所需要的最少时间
解题思路:
求树上部分点的最大直径
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair pii;
const ll mod = 1e9 + 7;
const int maxn = 1e5 + 10;
int n, k, ans, pos;
int pe[maxn];
vector a[maxn];
void dfs(int u, int dis, int fa){
if(pe[u] && dis>ans) pos = u, ans = dis;
for(auto v : a[u]){
if(v == fa) continue;
dfs(v, dis+1, u);
}
}
int main() {
scanf("%d%d", &n, &k);
for(int i=1; i
551 (Div. 2)___D Serval and Rooted Tree —— 树dp
题目大意:
一棵树,每个叶子有一个 1 1 1~ k k k 的数字,不重复且共有 k k k 个叶子,每个非叶子结点有一个 m i n min min 或 m a x max max 操作,代表从它的子节点中进行 m i n min min 操作或者 m a x max max 操作,问根节点的最大值是多少???
解题思路:
明显用数值进行操作是不行的,那么就用树 d p dp dp 的思想:
d p [ i ] dp[i] dp[i] 表示结点i保存着第 d p [ i ] dp[i] dp[i] 大的叶子结点,要使根节点最大,则 d p dp dp 维护的就是排名最小的叶子结点
代码思路:
有了 d p dp dp 的思路,对于两个操作:
若是 m a x max max ,则代表从子节点中取最大值,在 d p dp dp 所代表的的排名操作中,也就是取更小的排名,则 d p [ i ] = m i n ( d p [ j ] ) dp[i] = min(dp[j]) dp[i]=min(dp[j])
若是 m i n min min ,则代表从子节点中取最小值,由于 d p dp dp 表示的是排名,取最小值也就是两者的排名和(叶子从 1 1 1 到 k k k ),则 d [ i ] = s u m ( d p [ j ] ) d[i] = sum(dp[j]) d[i]=sum(dp[j])
最终的答案也就是 c n t + 1 − d p [ 1 ] cnt + 1 - dp[1] cnt+1−dp[1]
#include
using namespace std;
typedef long long ll;
const int maxn = 3e5+10;
int n, cnt, op[maxn], dp[maxn];
vector e[maxn];
void dfs(int k){
if(e[k].empty()) {
cnt++;
dp[k] = 1;
return ;
}
if(op[k]) dp[k] = INT_MAX; // 注意初始为 0 的情况
for(auto &i : e[k]){
dfs(i);
if(op[k]) dp[k] = min(dp[k], dp[i]);
else dp[k] += dp[i];
}
}
int main(){
scanf("%d", &n);
for(int i=1; i<=n; i++)
scanf("%d", op+i);
for(int i=2, tmp; i<=n; i++){
scanf("%d", &tmp);
e[tmp].push_back(i);
}
dfs(1);
printf("%d\n", cnt+1-dp[1]);
}
——斜率DP——
牛客第三场 J Wood Processing —— 斜率dp
题目大意:
n n n 块 w ∗ h w*h w∗h 的木块,分成 k k k 组
每组木块全部切成当组木块的最低高度
求最少浪费面积
解题思路:
首先按高度从高到低排序
d p [ i ] [ k ] dp[i][k] dp[i][k] 表示到第 i i i 块,分为 k k k 组的最大保留面积
答案即为 s u m − d p [ n ] [ k ] sum - dp[n][k] sum−dp[n][k],设 p r e pre pre 为宽度前缀和
d p [ i ] [ k ] = m a x ( d p [ j ] [ k − 1 ] + ( p r e [ i ] − p r e [ j ] ) × h [ i ] ) dp[i][k] = max(dp[j][k-1] + (pre[i] - pre[j]) \times h[i]) dp[i][k]=max(dp[j][k−1]+(pre[i]−pre[j])×h[i])
时间复杂度: O ( n 2 k ) O(n^2k) O(n2k)
考虑斜率优化,设 j 1 < j 2 < i j_1j1<j2<i
当 d p [ j 1 ] [ k − 1 ] + ( p r e [ i ] − p r e [ j 1 ] ) × h [ i ] dp[j_1][k-1] + (pre[i] - pre[j_1]) \times h[i] dp[j1][k−1]+(pre[i]−pre[j1])×h[i] < < < d p [ j 2 ] [ k − 1 ] + ( p r e [ i ] − p r e [ j 2 ] ) × h [ i ] dp[j_2][k-1] + (pre[i] - pre[j_2]) \times h[i] dp[j2][k−1]+(pre[i]−pre[j2])×h[i] 时
j 2 j_2 j2 比 j 1 j_1 j1 更优,则 j 1 j_1 j1 可以从决策集中删去
转化为 h [ i ] h[i] h[i] < < < d p [ j 2 ] [ k − 1 ] − d p [ j 1 ] [ k − 1 ] p r e [ j 2 ] − p r e [ j 1 ] \frac{dp[j_2][k-1] - dp[j_1][k-1]}{pre[j_2]-pre[j_1]} pre[j2]−pre[j1]dp[j2][k−1]−dp[j1][k−1]
h [ i ] h[i] h[i] 是单调递减的,因此维护一个单调递减的决策集
时间复杂度: O ( n k ) O(nk) O(nk)
核心:斜率优化dp
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
//#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
typedef long long ll;
using pii = pair ;
const int maxn = 5e3 + 5;
int n, k, q[maxn];
ll sum, pre[maxn];
ll dp[maxn][2019];
struct node{
int w, h;
bool operator < (const node &A){
return h > A.h;
}
} a[maxn];
long double slope(int x, int y, int p){
long double t = dp[x][p-1] - dp[y][p-1];
return t / (pre[x] - pre[y]);
}
int main() {
scanf("%d%d", &n, &k);
for(int i=1; i<=n; i++) scanf("%d%d", &a[i].w, &a[i].h);
sort(a+1, a+n+1);
for(int i=1; i<=n; i++){
pre[i] = pre[i-1] + a[i].w;
sum += 1ll * a[i].h * a[i].w;
}
for(int p=1; p<=k; p++){
int l = 0, r = 0;
for(int i=1; i<=n; i++){
while(l= a[i].h) l++;
dp[i][p] = dp[q[l]][p-1] + 1ll * (pre[i] - pre[q[l]]) * a[i].h;
while(l