树与图的遍历

1. 可达性统计
给定一张N个点M条边的有向无环图,分别统计从每个点出发能够到达的点的数量。
数据 1≤N,M≤30000 这里folyd 跑 不仅数组开不下 还有n^3的复杂度chun
关于 这个点每个状态的用矩阵肯定存不下这些关系 所以可以考虑用int 二进制来进行压缩
还有bitset STL 进行非常长长度二进制存储和运算
可达性就可以考虑 topsort了 找到循序序列 之后 倒着来吧每个点 或 一下 就 吧可达点 付到上一个 完成统计

#include 
#include 
#include 
using namespace std;
const int maxn = 3e4 + 5;

int n, m;
int head[maxn], cnt;
int to[maxn << 1], nxt[maxn << 1];
int deg[maxn], seq[maxn], k;
bitset f[maxn];

void ade(int a, int b){
    to[++ cnt] = b;
    nxt[cnt] = head[a];
    head[a] = cnt;
}

void topsort(){
    queue q;
    for(int i = 1; i <= n; i ++ ) 
        if(deg[i] == 0) q.push(i);
    while(!q.empty()){
        int x = q.front(); q.pop();
        seq[++ k] = x;
        for(int i = head[x]; i; i = nxt[i]) {
            int y = to[i];
            if(-- deg[y] == 0) q.push(y);
        }
    }
}

int main(){
    cin >> n >> m;
    for(int i = 1, a, b; i <= m; i ++ ) {
        cin >> a >> b;
        ade(a, b);
        deg[b] ++;
    }
    topsort();
    for(int i = n; i >= 1; i --) {
        int x = seq[i];
        f[x][x] = 1;
        for(int j = head[x]; j; j = nxt[j]) 
            f[x] |= f[to[j]];
    }
    for(int i = 1; i <= n; i ++) cout << f[i].count() << endl;
    return 0;
}

DFS

1. 小猫爬山
dfs 优化剪枝
首先 我们dfs 每次搜遍历一边考虑能装下 放入猫
这一操作完后 还原 进行不放入这个猫 加 一辆车的搜索

考虑优化 第一 我们排序每次选最大的 可以使开头第一搜索的选择性减低
显然 我们也可以加入 如果当前搜索车数量 大于min (n, ans) 直接return 放弃这个不好的解

#include 
#include 
using namespace std;
const int maxn = 100;
int n, m, ans;
int car[maxn], cat[maxn];

void dfs(int num, int cnt){
    if(cnt >= ans) return ;
    if(num == n  + 1) {
        ans = cnt;
        return ;
    }
    for(int i = 1; i <= cnt; i ++) {
        if(car[i] + cat[num] <= m) {
            car[i] += cat[num];
            dfs(num + 1, cnt);
            car[i] -= cat[num];
        }
    }
    car[cnt + 1] = cat[num];
    dfs(num + 1, cnt + 1);
    car[cnt + 1] = 0;
}

bool cmp(int a, int b) {return a > b;}

int main(){
    cin >> n >> m;
    for(int i = 1; i > cat[i];
    sort(cat + 1, cat + 1 + n, cmp);
    ans = n;
    dfs(1, 0);
    cout << ans << endl;
    return 0;
}

2. Sudoko (acwing数据)
这个题数据好强啊 不预处理位运算超时的
首先考虑 选择可选位置最少的框填入搜索
其次这题必须加入位运算 行列宫 9位2进制表示出来 lowbit 取出最低位 遍历可选值
位元算 记得还原全部状态再进行下次搜索

#include 
using namespace std;

const int N = 9;
int row[N], col[N];
int ones[1 << N], map[1 << N];
int g[5][5];
char str[105];

inline int lowbit(int x) {
    return x & -x;
}

inline int get(int x, int y) {
    return row[x] & col[y] & g[x/3][y/3];
}

bool dfs(int cnt) {
    if(cnt == 0) return 1;

    int minv = 10, x, y;
    for(int i = 0 ; i < N; i ++) for(int j = 0; j < N; j ++) 
        if(str[i * 9 + j] == '.') {
            int t = ones[get(i, j)];
            if(t < minv) minv = t, x = i, y = j;
        }
    for(int i = get(x, y); i; i -= lowbit(i)) {
        int t = map[lowbit(i)];
        row[x] -= 1 << t;
        col[y] -= 1 << t;
        g[x/3][y/3] -= 1 << t;
        str[x * 9 + y] = '1' + t;

        if(dfs(cnt - 1)) return 1;

        row[x] += 1 << t;
        col[y] += 1 << t;
        g[x/3][y/3] += 1 << t;
        str[x * 9 + y] = '.';

    }
    return 0;
}

void init(){
    for(int i = 0; i < 9; i ++) row[i] = col[i] = (1 << 9) - 1;
    for(int i = 0; i < 3; i ++) 
        for(int j = 0; j < 3; j ++) 
            g[i][j] = (1 << 9) - 1; 
}

int main(){
    for(int i = 0; i < N; i ++) map[1 << i] = i;
    for(int i = 0; i < 1 << N; i ++) {
        int s = 0;
        for(int j = i; j; j -= lowbit(j)) s ++;
        ones[i] = s;
    }

    while(cin >> str && str[0] != 'e') {
        init();
        int cnt = 0;
        for(int i =0, k = 0; i < N; i ++) 
            for(int j = 0; j < N; j ++, k ++ ) 
                if(str[k] != '.') {
                    int t = str[k] - '1';
                    row[i] -= 1 << t;
                    col[j] -= 1 << t;
                    g[i/3][j/3] -= 1 << t;
                }else cnt ++;

        dfs(cnt); 
        cout << str << endl;
    }
    return 0;
}

优化搜索

1. 小木棍
预先处理出所有木棍的总长度,且保证枚举答案的值能被总长度整除。
预先处理出最长的和最短的木棍的长度,搜索时从最大长度到最小长度递减枚举。
若拼接当前木棍时已用了一根长为X的木棍,则dfs时从长度X开始搜索。
若某组拼接不成立 且此时 已拼接的长度为0 或 当前已拼接的长度与刚才枚举的长度之和为最终枚举的答案
则可直接跳出循环,因为此时继续枚举其它更小的值时,显然可能情况更少,且同样凑不完。

#include 
#include 
#include 
using namespace std;
const int maxn = 50+5;

int n,m,x,sum,xz,flag,cnt;
bool cmp(int x,int y) {return x>y;}
bool vis[maxn];
int a[maxn];

void dfs(int len,int k,int lenth,int pos){
    if(flag) return ;
    if(k*lenth==sum){
        cout<<lenth<<endl;
        flag=1;
        return ;
    }
    if(sum-len<a[cnt]) return ;
    if(len==lenth){
        dfs(0,k+1,lenth,1);
        return ;
    }
    for(int i=pos;i<=cnt;i++){
        if(!vis[i]&&len+a[i]<=lenth){
            vis[i]=1;
            dfs(len+a[i],k,lenth,i+1);
            vis[i]=0;
            if(len==0||len+a[i]==lenth) break;
            while(a[i]==a[i+1])i++;
        }
    }
}

int main() { 
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>m;
        if(m<=50){
            a[++cnt]=m;
            sum+=m;
        } 
    }
    sort(a+1,a+1+cnt,cmp);
    xz=sum>>1;
    for(int i=a[1];i<=xz;i++){
        if(sum%i==0){
            dfs(0,0,i,1);
            if(flag) break;
        }
    }
    if(!flag) cout<<sum<<endl;
    return 0;
}

acwing (重写版)

#include 
#include 
using namespace std;
const int maxn = 100 + 5;
const int mod = 1e9 + 7;

int n, sum, len;
int a[maxn], cnt;
bool vis[maxn];

bool cmp(int a, int b) {
    return a > b;
}

bool dfs(int k, int len, int p, int lenth){
    if(k * lenth == sum) return 1;
    if(len == lenth) return dfs(k + 1, 0, 1, lenth);
    for(int i = p; i <= cnt; i++){
        if(!vis[i] && len + a[i] <= lenth){
            vis[i] = 1;
            if(dfs(k, len + a[i], i + 1, lenth)) return 1;
            vis[i] = 0;
            if(len == 0) return 0;
            if(len + a[i] == lenth) return 0;
            while(a[i] == a[i + 1] && i <= cnt) i++;
        }
    }
    return 0;
}

signed main() {
    while(cin >> n && n) {
        cnt = 0;
        len = 0;
        sum = 0;
        for(int i = 1, l; i <= n; i ++) {
            vis[i] = 0;
            cin >> l;
            if(l > 50) continue;
            sum += l;
            a[++ cnt] = l;
            len = max (len, l);
        }
        sort(a + 1, a + cnt, cmp);
        for(int i = len; i <= sum; i ++)
            if(sum % i == 0 && dfs(0, 0, 1, i)) {
                cout << i << endl;
                break;
            }
    } 
    return 0;
}

2. 奶油蛋糕
体积一定 让多层蛋糕表面积最小
最小体积+面积预处理 搜索超过限制return
从 上一层 最大r - 1,h - 1 开始搜索 情况树少
s + 2 * (n - v) / R[dep + 1] >= ans 放缩出来的公式 。。果然只加一层超了 再加也超

#include 
#include 
using namespace std;
const int maxn = 100 + 5;
const int mod = 1e9 + 7;

int n, m, ans;
int R[maxn], H[maxn]; 
int mins[maxn], minv[maxn];

void dfs(int dep, int v, int s){
    if(minv[dep] + v > n) return ;
    if(mins[dep] + s >= ans) return ;
    if(s + 2 * (n - v) / R[dep + 1] >= ans) return ;

    if(dep == 0) {
        if(v == n) ans = min(ans, s);
        return ;
    }

    for(int r = min((int)sqrt(n - v), R[dep + 1] - 1); r >= dep; r --) {
        for(int h = min((n - v) / r / r, H[dep + 1] - 1); h >= dep; h --) {
            R[dep] = r, H[dep] = h;
            dfs(dep - 1, v + r * r * h, s + 2 * r * h + (dep == m ? r * r : 0));
        }
    }
    return ;
}

signed main() {
    cin >> n >> m;
    for(int i = 1; i <= 25; i ++) {
        minv[i] = minv[i - 1] + i * i * i;
        mins[i] = mins[i - 1] + i * i * 2; 
    }
    R[m + 1] = H[m + 1] = 0x3f3f3f3f;
    ans = 0x3f3f3f3f;
    dfs(m, 0, 0);
    cout << ans << endl;
    return 0;
}

迭代加深

1. 加成序列
预先设置深度, 搜索再树浅位置解

#include 
using namespace std;
const int maxn = 105;

int n;
int path[maxn];

bool dfs(int u, int k){
    if (u == k) return path[u - 1] == n;
    bool vis[maxn] = {0};
    for (int i = u - 1; i >= 0; i -- )
        for (int j = i; j >= 0; j -- ){
            int s = path[i] + path[j];
            if (s > n || s <= path[u - 1] || vis[s]) continue;
            vis[s] = true;
            path[u] = s;
            if (dfs(u + 1, k)) return true;
        }
    return false;
}

int main(){
    path[0] = 1;
    while (cin >> n, n){
        int k = 1;
        while (!dfs(1, k)) k ++ ;
        for (int i = 0; i < k; i ++ ) cout << path[i] << ' ';
        cout << endl;
    }
    return 0;
}
双向搜索

2. 送礼物
先优化搜索顺序
其次 先搜前一半 dfs出所有情况
再dfs后面n个 这样二分查可能最大值 不用在意dp数据量太。。。。orz

#include 
#include 
using namespace std;
typedef long long ll;
const int maxn = 105;

int n, m, ans, k;
int w[maxn];
int wes[1 << 24], cnt;

void dfs1(int u, int s) {
    if(u == k) {
        wes[cnt ++] = s;
        return ;
    }
    if((ll)s + w[u] <= m) dfs1(u + 1, s + w[u]);
    dfs1(u + 1, s);
}

void dfs2(int u, int s) {
    if(u == n) {
        int l = 0, r = cnt - 1;
        while(l < r){
            int mid = l + r + 1 >> 1;
            if((ll)wes[mid] + s <= m) l = mid;
            else r = mid - 1;
        }
        if((ll)wes[l] + s <= m) ans = max(ans, wes[l] + s);
        return ;
    }
    if((ll)s + w[u] <= m) dfs2(u + 1, s + w[u]);
    dfs2(u + 1, s);
}

bool cmp(int a, int b) {return a > b;}

int main(){
    cin >> m >> n;
    for(int i = 0; i > w[i];
    sort(w, w + n, cmp);
    k = n/2 + 2;
    dfs1(0, 0);

    sort(wes, wes + cnt);
    cnt = unique(wes, wes + cnt) - wes;
    dfs2(k, 0);
    cout << ans << endl;
    return 0;
}