905

emmmmm, 这是一次比较有意义的题吧, 最后我的排名很靠前, 但是成绩也不是很如人意。。。

这是第一道题, 一般会比较简单一点, 但却是我三道题中得分最低的一个, 只有30分的暴力分。 当时写的时候直接暴力搜索, 选出所有的情况并记录, 复杂度为 O(T · 2 n )。 

我们先看k = 2的特例, 我们可以想到, 在n个位置中放m的小球, 且小球的间距 >= 1, 即留出一个间隔。假设我们就在前m个位置上放上小球而此时的方案数是C(m – 2,n – 2 – (m – 1) ), (C为组合数),

那我们就推广到k的做法, 即0两个球之间间距 >= k – 1, 方案数为C(m – 2, n – 2 – (m – 1)* (k – 1)),由于只用判断方案数的奇偶性, 由卢卡斯定理可得, 在%p的情况下, C(n, m) = C(n % p, m % p)* C(n / p, m / p);当p = 2时, C(n, m)共有4种情况, 当m >= n 时, C(n, m) = 1; 所以用卢卡斯定理继续递归下去, 当m & n = n, (即n的每一位的1在m中都是1),则C(n, m)的结果为1, 否则为0; 所以该题的正解就是判断((n – 2 – (m – 1) * (k – 1)) & (m – 2)) == m – 2是否成立。


#include 

using namespace std;

template < typename T > inline void read(T &x) {
    x = 0; T ff = 1, ch = getchar();
    while(!isdigit(ch)) {
        if(ch == '-') ff = -1;
        ch = getchar();
    }
    while(isdigit(ch)) {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x *= ff;
} 

int T, n, m, k;

int main() {
    read(T);
    while(T--) {
        read(n); read(m); read(k);
        if(((n - 2 - (m - 1) * (k - 1)) & (m - 2)) == m - 2) puts("Yes");
        else puts("No");
    }    
    return 0;
} 

正解

#include 

using namespace std;

template < typename T > inline void read(T &x) {
    x = 0; T ff = 1, ch = getchar();
    while(!isdigit(ch)) {
        if(ch == '-') ff = -1;
        ch = getchar();
    }
    while(isdigit(ch)) {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x *= ff;
} 

int T, n, m, k;
bool ans = false;

void dfs(int now, int val) {
    if(now == m + 1) {
        if(val == n) ans ^= 1;
        return ;
    }
    for(int i = n - (m - now) * k - val; i >= k; --i) {
        if(now == m && val + i < n) continue;
        dfs(now + 1, val + i);
    }
}

int main() {    
    freopen("phantasm.in", "r", stdin);
    freopen("phantasm.out", "w", stdout);
    read(T);
    while(T--) {
        read(n); read(m); read(k);
        dfs(2, 1);
        if(ans) puts("Yes");
        else puts("No");
        ans = false;
    }
    return 0;
} 

搜素大法好(30分)

emmmm,标题就省略了, 题意显然易懂, 一个很显然的做法就是用lca求距离, O(n)枚举每一个点求最小值, 时间复杂度为O(n * q * logn), 有60分的分数。 另有20分的大数据是一条链的情况, 当时就怎么也推不出来, 就导致我的第一题的特殊情况没有判断,这道题的正解是树形DP,

对于有根树,一个点所选择的对应点仅在其子树内的情形, 答案可以通过树形 dp 递推得到。 考虑选择其子树外的点的情形。观察原式可以发现,如果一 个点的父亲的最优选择不是该点,则其在子树外的 最优选择必 然就是其父亲的最优选择。否则,其在子树外的最优选择必然是 其父亲的次优选择。由此,可在 dfs 过程中自上而下推出所有点 在其子树外的最优答案。 对每个点子树内外的最优答案取最小值,即可得到每个点的 答案。 复杂度:O(n)。


#include 

using namespace std;

typedef long long ll;
const int INF = 1e9 + 7;
const int MAXN = 2e5 + 100;
const int MAXM = 3e3 + 10;
const double eps = 1e-5;

template < typename T > inline void read(T &x) {
    x = 0; T ff = 1, ch = getchar();
    while(!isdigit(ch)) {
        if(ch == '-') ff = -1;
        ch = getchar();
    }
    while(isdigit(ch)) {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x *= ff;
} 

template < typename T > inline void write(T x) {
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) write(x / 10);
    putchar(x % 10 + '0'); 
} 

int n, T, c[MAXN], deep[MAXN], f[MAXN][20], dis[MAXN];
int lin[MAXN], tot = 0;
struct edge {
    int y, v, next;
}e[MAXN << 1];

inline void add(int xx, int yy, int vv) {
    e[++tot].y = yy;
    e[tot].v = vv;
    e[tot].next = lin[xx];
    lin[xx] = tot;
}

inline void BFS() {
    queue < int > q;
    q.push(1);
    deep[1] = 1;
    while(!q.empty()) {
        int x = q.front(); q.pop();
        for(int i = lin[x], y; i; i = e[i].next) {
            if(!deep[y = e[i].y]) {
                q.push(y);
                deep[y] = deep[x] + 1;
                f[y][0] = x;
                dis[y] = dis[x] + e[i].v;
                for(int i = 1; i <= 19; ++i) {
                    f[y][i] = f[f[y][i - 1]][i - 1];
                }
            }
        }
    } 
}

inline int lca(int x, int y) {
    if(deep[x] > deep[y]) swap(x, y);
    for(int i = 19; i >= 0; --i) {
        if(deep[f[y][i]] >= deep[x])
            y = f[y][i];
    }
    if(x == y) return x;
    for(int i = 19; i >= 0; --i) {
        if(f[x][i] != f[y][i]) 
            x = f[x][i], y = f[y][i];
    } 
    return f[x][0];
}

inline int dist(int x, int y) {
    return dis[x] + dis[y] - 2 * dis[lca(x, y)]; 
} 
 

int main() {    
    freopen("skylines.in", "r", stdin);
    freopen("skylines.out", "w", stdout);
    read(n);
    for(int i = 1; i <= n; ++i) {
        read(c[i]);
    }
    for(int i = 1; i < n; ++i) {
        int u, v, w;
        read(u); read(v); read(w);
        add(u, v, w);
        add(v, u, w);
    }
    BFS();
    read(T);
    while(T--) {
        int u;
        read(u);
        int minn = INF;
        for(int i = 1; i <= n; ++i) {
            if(i == u) continue;
            if(dist(u, i) + c[u] - c[i] < minn) minn = dist(u, i) + c[u] - c[i];
        }
        write(minn);
        puts("");
    }
    return 0;
} 

lca(60分)

题意较难理解, 所以可能好多人没有理解题意导致失分, 首先想到的还是搜索大法, 可能的数列共有 m! 种,答案就是所有情况下数列的权值和 除以情况数。 搜索枚举每种情况并求出其权值和,全部加起来除以 m! 即 可。 复杂度:O(m!)。 可以获得 30 分。

还有对所有 ai 均相等的算法 由于 ai 都相等,则对于任意数列,其权值和均相等 直接输出 m ∗ ai 即可 综上,可以获得 50 分,这可是送的20分。。。

然后大佬们就想到了状态压缩, 一个数列的权值和与数列中数的顺序无关,只与每种数的个 数有关 考虑 dp,状态中需要记录每种数的个数 可以发现,将得到的数列排序后,相邻两数的差只能为 0 或 1 用二进制序列维护原数列的差分数组,即可表示数列中每种 数的个数,f(i, S) 表示生成了 i 个数,已有数列的差分为 S 的方案数 则每种数列出现的概率 PS = f(m,S) /m! 转移时枚举前一个数的大小,修改状态即可 事先预处理出 totS 表示状态 S 所表示的数列的权值和, 最 后的答案即为 ∑ S PStotS 复杂度:O(m · 2 m)。注意做除法时用乘法逆元计算。


#include 

using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e5 + 100;
const int MAXM = 3e3 + 10;
const double eps = 1e-5;

template < typename T > inline void read(T &x) {
    x = 0; T ff = 1, ch = getchar();
    while(!isdigit(ch)) {
        if(ch == '-') ff = -1;
        ch = getchar();
    }
    while(isdigit(ch)) {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x *= ff;
} 

template < typename T > inline void write(T x) {
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) write(x / 10);
    putchar(x % 10 + '0'); 
} 

const ll mod = 998244353;
ll n, m, sum[MAXN], a[MAXN], b[MAXN];
ll ans, cnt;
bool flag = false;

inline void dfs(int now) {
    if(now == m + 2) {
        ans += sum[now - 1];
        if(ans >= mod) ans %= mod;
        cnt++;
//        printf("ans = %d cnt = %d\n", ans, cnt);
        return ;
    }
    for(int i = 1; i < now; ++i) {
        a[now] = a[i] + 1;
        sum[now] = sum[now - 1] + b[a[now]];
        dfs(now + 1);
        a[now] = 0;
        sum[now] = 0;
    }
}

int main() {    
//    freopen("kiseki.in", "r", stdin);
//    freopen("kiseki.out", "w", stdout);
    read(n); read(m);
    for(int i = 2; i <= n + 1; ++i) {
        read(b[i]);
        if(i != 2 && b[i] != b[i - 1]) flag = true;
    }
    if(!flag) {
        write((m * b[2]) % mod);
        return 0;
    }
    sum[2] = b[2];
    a[1] = 1;
    a[2] = 2; 
    dfs(3); 
//    printf("cnt = %d\n", cnt);  
    for(int i = 0; ; ++i) {
        if((ans + i * mod) % cnt == 0) {
            write((ans + i * mod) / cnt);
            return 0; 
        } 
    } 
    return 0; 
} 

还是搜索啊~