怀化学院第十五届大学生计算机程序设计竞赛题解

A-Fibonacci   

        递推式和矩阵乘法

        斐波那契数列有递推公式


        我们可以把这个计算过程抽象成一个矩阵运算的过程。

        那么对于第 n 项,我们有:


快速幂

        对于一个指数为正整数的幂运算,我们有:


        依次递推,我们可以把幂运算的复杂度,从 O(n)降低到 O(\log_2n)
        而我们又知道矩阵乘法运算是符合结合律的,所以可以使用快速幂。

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define PII pair<int,int>
#define x first
#define y second
#define endl '\n'

const int N=1e6+10,M=2*N,p=1e9+7,INF=0x3f3f3f3f;

int qmul(int a, int b)
{
	int res = 0;
	while (b)
	{
		if (b & 1) res = (res + a) % p;
		a = (a + a) % p;
		b >>= 1;
	}
	return res;
}

void mul(int c[][2], int a[][2], int b[][2])  // c = a * b
{
	static int t[2][2];
	memset(t, 0, sizeof t);
	
	for (int i = 0; i < 2; i ++ )
		for (int j = 0; j < 2; j ++ )
			for (int k = 0; k < 2; k ++ )
				t[i][j] = (t[i][j] + qmul(a[i][k], b[k][j])) % p;
	
	memcpy(c, t, sizeof t);
}

int F(int n)
{
	if (!n) return 0;
	
	int f[2][2] = {1, 1};
	int a[2][2] = {
		{0, 1},
		{1, 1},
	};
	
	for (int k = n - 1; k; k >>= 1)
	{
		if (k & 1) mul(f, f, a);  // f = f * a
		mul(a, a, a);  // a = a * a
	}
	
	return f[0][0];
}


void solve()
{
	int n;
	cin>>n;
	cout<<F(n)<<endl;
	
	return;
}

signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0),cout.tie(0);
	
	int T=1;
	cin>>T;
	while(T--)
		solve();
	
	return 0;
}

B-传送

        设 元形式幂级数 G(x_1,x_2,...,x_n)

                                G(x_1,x_2,..,x_n)=\sum_{i_1,i_2,...,i_k}c_{i_1,i_2,...,i_k}x_{1}^{i_1}x_{1}^{i_1}***x_{n}^{i_n}
其中 c_{i_1,i_2,...,i_k} 表示向量 (i_1,i_2,...,i_k) 作为技能出现的次数。
        我们希望求出 F(x_1,x_2,...,x_n) 表示:

                                F(x_1,x_2,...,x_n)=\sum_{i=0}^{\infty }G(x_1,x_2,...,x_n)^i=\frac{1}{1-G(x_1,x_2,...,x_n)}
那么其中 F(x_1,x_2,...,x_n) 的系数 d_{i_1,i_2,...,i_k} 则表示利用技能集合凑出向量 (i_1,i_2,...,i_k) 的方案数。
        对于存在障碍物的方案数求解我们只需要 O(m^2) 的容斥即可,设 f_i 表示到达第 个点的方案数。

                                f_i=ways(p_i)-\sum_{j,p_i \leq p_j }f_j
这里 p_i 表示第 i 个关键点的坐标向量,ways(p_i) 表示组合出向量 p_i 的方案数,定义 k 维向量的 p_i 小于 p_i 为每一维均小于,同时 p_i-p_j 为每一维相减。
        现在我们只需要解决一个问题就是 k 元形式幂级数的求逆。
        首先解决 k 元多项式乘法,对于常见的 k 比较小的情况可以将每一维的值域扩张成两倍,然后直接做卷积并舍弃进位的情况即可。
        对于 k 较大的情况,我们多引入一维 t , 并引入占位函数 \chi (i)=\left \lfloor \frac{i}{n_1} \right \rfloor + \left \lfloor \frac{i}{n_1n_2} \right \rfloor+...+\left \lfloor \frac{i}{n_1n_2...n_{k-1}} \right \rfloor ,将函数表示成 \sum _{i}f_ix^it^{\chi (i)}
        多引入一个元可以增强对卷积的限制,我们原先的问题在于我们将向量重标号后,进行加法之后的标号会产生进位并对答案产生影响,在引入 之后还需要满足 \chi (i)+\chi (j)=\chi (x+j) ,注意到有 \left \lfloor \frac{i}{x} \right \rfloor+\left \lfloor \frac{j}{x} \right \rfloor-\left \lfloor \frac{i+j}{x} \right \rfloor\in \{-1,0\},所以 \{\chi (i-j)+\chi(j)\} 合法的集合很小。于是我们只需要计算 \sum _{i}f_ix^it^{\chi (i)} 在 mod(t^k-1) 意义下的多项式乘法,也就是循环卷积。
        由于 k\leq \log_2N ,那么就可以对每一维度做长度为 2N 的 DFT,然后对于 t 暴力做卷积。对于求逆,只需像一元多项式一样利用牛顿迭代求解即可。求导也是一致的。
        时间复杂度为 O(kN\log N)

#include <bits/stdc++.h>
 
using namespace std;
using i64 = long long;
 
template<int MOD>
struct ModInt {
    int x;
 
    ModInt() : x(0) {}
    ModInt(i64 y) : x(y >= 0 ? y % MOD : (MOD - (-y) % MOD) % MOD) {}
 
    inline int inc(const int &x) {
        return x >= MOD ? x - MOD : x;
    }
    inline int dec(const int &x) {
        return x < 0 ? x + MOD : x;
    }
 
    ModInt &operator+= (const ModInt &p) {
        x = inc(x + p.x);
        return *this;
    } 
    ModInt &operator-= (const ModInt &p) {
        x = dec(x - p.x);
        return *this;
    }
 
    ModInt &operator*= (const ModInt &p) {
        x = (int)(1ll * x * p.x % MOD);
        return *this;
    }
    ModInt &operator/= (const ModInt &p) {
        *this *= p.inverse();
        return *this;
    }
 
    ModInt operator-() const { return ModInt(-x); } 
 
    friend ModInt operator + (const ModInt& lhs, const ModInt& rhs) {
        return ModInt(lhs) += rhs;
    }
    friend ModInt operator - (const ModInt& lhs, const ModInt& rhs) {
        return ModInt(lhs) -= rhs;
    }
    friend ModInt operator * (const ModInt& lhs, const ModInt& rhs) {
        return ModInt(lhs) *= rhs;
    }
    friend ModInt operator / (const ModInt& lhs, const ModInt& rhs) {
        return ModInt(lhs) /= rhs;
    }
 
    bool operator == (const ModInt &p) const { return x == p.x; } 
    bool operator != (const ModInt &p) const { return x != p.x; }
 
    ModInt inverse() const {
        int a = x, b = MOD, u = 1, v = 0, t;
        while(b > 0) {
            t = a / b;
            swap(a -= t * b, b);
            swap(u -= t * v, v);
        }
        return ModInt(u);
    }
 
    ModInt pow(i64 n) const {
        ModInt ret(1), mul(x);
        while(n > 0) {
            if(n & 1) ret *= mul;
            mul *= mul;
            n >>= 1;
        }
        return ret;
    }
 
    friend ostream &operator<<(ostream &os, const ModInt &p) {
        return os << p.x;
    }
 
    friend istream &operator>>(istream &is, ModInt &a) {
        i64 t;
        is >> t;
        a = ModInt<MOD>(t);
        return (is);
    }
    static int get_mod() { return MOD; }
};
 
const int MOD = 998244353;
using MInt = ModInt<MOD>;
 
inline int add(int x, int y) {
    return x + y >= MOD ? x + y - MOD : x + y;
}
 
inline void addx(int &x, int y) {
    x = add(x , y);
}
 
inline int sub(int x, int y) {
    return x - y < 0 ? x - y + MOD : x - y;
}
 
inline void subx(int &x, int y) {
    x = sub(x , y);
}
 
inline int mul(int x, int y) { return 1ull * x * y % MOD; }
 
inline int fpow(int x, int y) {
    int ans = 1;
    while (y) {
        if (y & 1) ans = mul(ans, x);
        y >>= 1; x = mul(x, x);
    }
    return ans;
}
 
vector <int> roots, rev;
 
void getRevRoot(int base) {
    int n = 1 << base;
    if ((int)roots.size() == n) return;
    roots.resize(n); rev.resize(n);
    for (int i = 1; i < n; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (base - 1));
    for (int mid = 1; mid < n; mid <<= 1) {
        int wn = fpow(3, (MOD - 1) / (mid << 1));
        roots[mid] = 1;
        for (int i = 1; i < mid; i++) roots[i + mid] = mul(roots[i + mid - 1], wn);
    }
}
 
void ntt(vector <int> &a, int base) {
    int n = 1 << base;
    for (int i = 0; i < n; i++) {
        if (i < rev[i]) {
            swap(a[i], a[rev[i]]);
        }
    }
    for (int mid = 1; mid < n; mid <<= 1) {
        for (int i = 0; i < n; i += (mid << 1)) {
            for (int j = 0; j < mid; j++) {
                int x = a[i + j], y = mul(a[i + j + mid], roots[mid + j]);
                a[i + j] = add(x, y); a[i + j + mid] = sub(x, y);
            }
        }
    }
}
 
const i64 P = 1ll * MOD * MOD;
const int N = 1001 * 1001 + 5, M = 22, U = 1005;
 
vector <int> F[M], G[M], tmp, ANS;
int n[M], facn[M], nt[N];
map<vector<int> , int> suf , ways;
int lim, k, dis;
 
vector<int> da;
void dfs1(int u) {
    if (u == k + 1) {
        int id = 0;
        for (int i = 1; i <= k; i++) id += da[i] * facn[i - 1];
            // cout << id << endl;
        int flag = 1;
        for (int i = 1 ; i <= k ; i++) {
            flag &= da[i] == 0;
        }
        if (flag == 1) tmp[id] = 1;
        else {
            if (suf.count(da)) tmp[id] = MOD - suf[da];
        }
        return;
    }
    for (int i = 0; i < n[u]; i++) {
        da[u] = i;
        dfs1(u + 1);
    }
}
 
void dfs2(int u) {
    if (u == k + 1) {
        int id = 0;
        for (int i = 1; i <= k; i++) id += da[i] * facn[i - 1];
        ways[da] = ANS[id];
        return;
    }
    for (int i = 0; i < n[u]; i++) {
        da[u] = i;
        dfs2(u + 1);
    }
}
 
vector <int> mul(vector <int> a, vector <int> b, int need, int ntted = 0, int limit = 0) {
    int len = (int)a.size() + (int)b.size() - 1, base = 0;
    if (limit) len = limit;
    while ((1 << base) < len) ++base;
    getRevRoot(base);
    for (int i = 0; i < k; i++) {
        F[i].clear(); F[i].resize(1 << base);
        if (!ntted) {
            G[i].clear(); G[i].resize(1 << base);
        }
    }
    for (int i = 0; i < (int)a.size(); i++) F[nt[i]][i] = a[i];
    if (!ntted) {
        for (int i = 0; i < (int)b.size(); i++) G[nt[i]][i] = b[i];
    }
    for (int i = 0; i < k; i++) {
        ntt(F[i], base);
        if (!ntted) ntt(G[i], base);
    }
    for (int i = 0; i < (1 << base); i++) {
        static i64 res[M];
        memset(res, 0, sizeof(res));
        for (int j = 0; j < k; j++) {
            for (int t = 0; t < k; t++) {
                int go = (j + t >= k ? j + t - k : j + t); 
                res[go] += 1ll * F[j][i] * G[t][i];
                if (res[go] >= P) res[go] -= P;
            }
        }
        for (int j = 0; j < k; j++) F[j][i] = res[j] % MOD;
    }
    int inv = fpow(1 << base, MOD - 2);
    for (int i = 0; i < k; i++) {
        ntt(F[i], base);
        reverse(F[i].begin() + 1, F[i].end());
    }
    vector <int> ans(need);
    for (int i = 0; i < need; i++) ans[i] = mul(inv, F[nt[i]][i]);
    return ans;
}
 
vector <int> pinv(vector <int> a, int n) {
    a.resize(n);
    if (n == 1) {
        vector <int> ans(1, fpow(a[0], MOD - 2));
        return ans;
    }
    vector <int> f0 = pinv(a, (n + 1) >> 1);
    vector <int> tmp = mul(a, f0, n, 0, n), ans = f0;
    for (int i = 0; i < (int)f0.size(); i++) tmp[i] = 0;
    tmp = mul(tmp, f0, n, 1, n); ans.resize(n);
    for (int i = (int)f0.size(); i < n; i++) ans[i] = sub(0, tmp[i]);
    return ans;
}
 
int main() {
    cin.tie(nullptr)->sync_with_stdio(0);
    int T;
    cin >> T;
 
    while (T--)
    {
        cin >> k;
        for (int i = 1 ; i <= k ; i++) {
            cin >> n[i];
            n[i]++;
        }
        int _ , __;
        cin >> _ >> __;
        suf.clear() , ways.clear();
        while(_--){
            vector<int> d(k + 1);
            for (int i = 1 ; i <= k ; i++) {
                cin >> d[i];
            }
            suf[d]++;
        }
        facn[0] = 1;
        for (int i = 1; i <= k; i++) facn[i] = facn[i - 1] * n[i];
        lim = facn[k];
        tmp.clear() , tmp.resize(lim);
        da.clear() , da.resize(k + 1);
        dfs1(1);
        for (int i = 0; i < lim; i++) {
            int res = 0;
            for (int j = 1; j < k; j++) res += i / facn[j];
            nt[i] = res % k;
        }
        ANS = pinv(tmp, lim);
        dfs2(1);
        vector<MInt> f(__ + 1);
        vector<vector<int>> v(__ + 1);
 
 
        v[0] = vector<int>(k + 1);
 
        for (int i = 1 ; i <= k ; i++) {
            v[0][i] = ::n[i] - 1;
        }
        for(int i = 1 ; i <= __ ; i++) {
            v[i] = vector<int>(k + 1);
            for (int j = 1 ; j <= k ; j++) {
                cin >> v[i][j];
            }
        }
 
        sort(v.begin() , v.end());
 
        auto check = [&](vector<int> a , vector<int> b) {
            int flag = 1;
            for (int i = 1 ; i <= k ; i++) {
                flag &= a[i] <= b[i];
            }
            return flag;
        };
 
        auto sub = [&](vector<int> a , vector<int> b) {
            auto ret = a;
            for (int i = 1 ; i <= k ; i++) {
                ret[i] -= b[i];
            }
            return ret;
        };
 
        for(int i = 0 ; i <= __ ; i++){
            f[i] = ways[v[i]];
            for(int j = 0 ; j < i ; j++) {
                if(check(v[j] , v[i])) {
                    f[i] -= f[j] * ways[sub(v[i] , v[j])];
                }
            }
        }
        cout << f[__] << '\n';
    }
}

C-生成树问题

        令第 i 种权值的边有 c_i 条。首先考虑二分答案,二分答案后则对于每种权值 i,限制
不能取超过 x_i 条边。不妨将问题转化为,限制每种权值的边不能删去超过 {x_i}'=c_i-x_i 条边,并且要求删去选出来的边之后全图联通。不难发现,存在一棵生成树满足条件(权值 i 出现次数不超过 x_i ),等价于在新的限制下,能删去的边的总数等于 \sum {x_i}'。另一方面,删除边集小于等于 x_i 和删边后联通均为拟阵,即可使用拟阵交解决问题。复杂度O(n^3\log(w))

#include<bits/stdc++.h>
using namespace std;
const int N = 505;
int n , m;
map<int,vector<pair<int,int>>>E;
map<int,int> cnt , limit;
typedef long long ll;
struct uni {
    int p[N];
    int cnt ;
    void init() {
        for(int i = 1;i <= n;i++) p[i] = i;
        cnt = n;
    }
    int Find(int x)
    {
        return p[x] == x ? x : p[x] = Find(p[x]) ;
    }
    bool Merge(int u,int v)
    {
        u = Find(u) , v = Find(v);
        if(u == v) return 0;
        p[u] = v; cnt--;
        return 1;
    }
    bool in(int u,int v)
    {
        return Find(u) == Find(v);
    }
}Un[N];
struct edge {
    int u , v , w;
};
bool mat[N][N];
int dis[N] , from[N] , mark[N];
bool tar[N];
queue<int> q;
vector<edge> I , rI;
bool bfs()
{
    for(int i = 1;i <= m;i++) mark[i] = 0 , from[i] = -1;
    int t = -1;
    while(q.size() && t == -1) {
        int u = q.front() ; q.pop();
        if(tar[u]) {t = u ; break ;}
        for(int i = 1;i <= m;i++) {
            if(mat[u][i] && dis[i] > dis[u] + 1) {
                dis[i] = dis[u] + 1; from[i] = u;
                if(tar[i]) {t = i ; break ;}
                q.push(i);
            }
        }
    }
    while(q.size()) q.pop();
    if(t == -1) {return 0;}
    while(t != -1) {
        mark[t] = 1 ; t = from[t];
    }
    vector<edge> new_I , new_rI;
    for(int i = 0;i < I.size();i++) {
        if(mark[i + 1]) new_rI.push_back(I[i]);
        else new_I.push_back(I[i]);
    }
    for(int i = 0;i < rI.size();i++) {
        if(mark[i + 1 + I.size()]) new_I.push_back(rI[i]);
        else new_rI.push_back(rI[i]);
    }
    swap(I , new_I) ; swap(rI , new_rI);
    return 1;
}
bool chk(ll lm)
{
    int sum = 0;
     I.clear() ; rI.clear();
    for(auto &[w , v] : E) {
        limit[w] = max(0LL , (ll)v.size() - lm / w) ;
        sum += limit[w];
    }
    Un[0].init();
    map<int,vector<int> > take;
    cnt.clear();
    for(auto &[w , v] : E) {
        int a,b;
        take[w].resize(v.size()); cnt[w] = v.size();
        for(int i = 0;i < v.size();i++) {
            a = v[i].first , b = v[i].second;
            if(!Un[0].in(a , b)) {
                Un[0].Merge(a , b);
                take[w][i] = 1;
                cnt[w]--;
            }
        }
    }
    for(auto &[w , v] : E) {
        int a , b;
        for(int i = 0;i < v.size() && cnt[w] > limit[w];i++) {
            if(take[w][i]) continue;
            a = v[i].first , b = v[i].second ;
            take[w][i] = 1 ; cnt[w]--;
        }
    }
    for(auto &[w , v] : E) {
        int a , b;
        for(int i = 0;i < v.size() ;i++) {
            a = v[i].first , b = v[i].second ;
            if(take[w][i]) {
                rI.push_back((edge){a , b , w});
            }
            else {I.push_back((edge){a , b , w}); }
        }
    }
    ///F1 : connect
    ///F2 : num limit
    int cc = 0;
    do{
     /*   printf("Round I : \n");
        for(auto S : I) {
            printf("%d %d %d\n",S.u,S.v,S.w);
        }
        printf("Round rI : \n");
        for(auto S : rI) {
            printf("%d %d %d\n",S.u,S.v,S.w);
        }*/
        cnt.clear() ;cc++;
        for(auto &S : I) cnt[S.w]++;
        for(int j = 1;j <= rI.size();j++) {
            Un[j].init() ;
            for(int i = 0;i < rI.size();i++) {
                if(i != j - 1) Un[j].Merge(rI[i].u , rI[i].v);
            }
        }
        for(int i = 1;i <= m;i++) for(int j = 1;j <= m;j++) mat[i][j] = 0;
        int tlm[505];
        for(int j = 0;j < rI.size();j++) tlm[j] = limit[rI[j].w];
        for(int i = 1;i <= I.size();i++) {
            for(int j = 1;j <= rI.size();j++) {
                ///i -> j , I\{i} + {j} in F1
                if(Un[j].cnt == 1 || (Un[j].cnt == 2 && !Un[j].in(I[i - 1].u , I[i - 1].v))) {mat[i][j + I.size()] = 1;}
                ///j -> i , I\{i} + {j} in F2
                if(I[i - 1].w == rI[j - 1].w) mat[j + I.size()][i] = 1;
                else if(cnt[rI[j - 1].w] < tlm[j - 1]) {mat[j + I.size()][i] = 1;}
            }
        }
        //for(int i = 1;i <= m;i++ , printf("\n")) for(int j = 1;j <= m;j++) printf("%d ",mat[i][j]);
        for(int i = 1;i <= m;i++) tar[i] = 0 , dis[i] = 1e9;
        for(int j = 1;j <= rI.size();j++) {
            if(Un[j].cnt == 1) {dis[j + I.size()] = 0 , q.push(j + I.size()); /*printf("IN_F1 %d\n",j);*/}
            if(cnt[rI[j - 1].w] < tlm[j - 1]) {tar[j + I.size()] = 1; /*printf("IN_F2 %d\n",j);*/}
        }
    }while(bfs());
    if(I.size() == sum) return 1;
    return 0;
}
ll set_min(ll x)
{
    ll sol = 0;
    for(auto &[w,v] : E) {
        sol = max(sol , x/w * w);
    }
    return sol;
}
ll set_max(ll x)
{
    ll sol = 1e18;
    for(auto &[w , v] : E) {
        sol = min(sol , (x/w + 1) * w - 1);
    }
    return sol;
}
void solve()
{
    cin >> n >> m;
    E.clear(); cnt.clear() ; limit.clear();
    ll r = 0;
    for(int i = 1;i <= m;i++) {
        int u , v , w;cin >> u >> v >> w;
        E[w].push_back(pair<int,int>{u , v});
        r = max(r , (ll)(1LL*w*E[w].size()));
    }
    ll l = 1;
    while(l < r) {
        ll md = (l + r >> 1);
        if(chk(md)) {r = md; r = max(l,set_min(r));}
        else {l = min(r , set_max(md) + 1);}
        //cout <<l<<' ' << r <<' ' <<r - l <<'\n';
    }
    cout << l << '\n';
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out2.txt","w",stdout);
    ios::sync_with_stdio(false) ; cin.tie(0) ; cout.tie(0);
    int t;cin >> t;
    while(t--) solve();
}

D-城市建造者

        

#include<bits/stdc++.h>
using namespace std;

const int _ = 1e5 + 5;
const int __ = 2e5 + 5;

int n, m;

int p[_];
bool vis[__];
int u[__], v[__];

int dsu(int x) {
  return p[x] == x ? x : p[x] = dsu(p[x]);
}

int main() {
  #ifdef Sakuyalove
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
  #endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int T;
  cin >> T;
  while (T--) {
    memset(vis, 0, sizeof vis);
    memset(p, 0, sizeof p);
    memset(u, 0, sizeof u);
    memset(v, 0, sizeof v);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
      p[i] = i;
    }
    for (int i = 1; i <= m; i++) {
      cin >> u[i] >> v[i];
    }
    int tot = m;
    for (int i = m; i >= 1; i--) {
      int x = dsu(u[i]), y = dsu(v[i]);
      if (x != y) {
        tot--;
        vis[i] = 1;
        p[x] = y;
      }
    }
    cout << tot << endl;
    for (int i = 1; i <= m; i++) {
      if (!vis[i]) cout << i << ' ';
    }
    cout << endl;
  }
  return 0;
}

E-A#B(签到题)

        很明显题目是要求 x^n,但是出题人很狗,把暴力做法卡掉了,所以套个快速幂板子就能过啦。


#include<bits/stdc++.h>
using namespace std;

#define int long long
#define PII pair<int,int>
#define x first
#define y second
#define endl '\n'

const int mod=1e9+7;

int qmi(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1)
            res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}

signed main() {
    int n,a,b;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a>>b;
        cout<<"Case #"<<i<<" : "<<qmi(a,b)<<endl;
    }
}
// 64 位输出请用 printf("%lld")

F-阿华的面试题

        乍一看这道题像是最短路问题,但是仔细分析会发现直接用最短路算法会超时,该死的出题人。画个比较复杂的样例之后,你会惊奇的发现这题居然是LCA,于是我们只需要套用板子就行了,可以参考AcWing_1171。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;

#define PII pair<int,int>
const int N=100010,M=N*2;

int h[N],w[M],e[M],ne[M],idx;
int dist[N],p[N],res[N];
int st[N],n,m;
vector<PII> query[N];

void add(int a,int b,int c)
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

int find(int x)
{
	if(x==p[x])return x;
	return p[x]=find(p[x]);
}

void dfs(int u,int fa)
{
	for(int i=h[u];~i;i=ne[i])
	{
		int j=e[i];
		if(j==fa)continue;
		dist[j]=dist[u]+w[i];
		dfs(j,u);
	}
}

void tarjan(int u)
{
	st[u]=1;
	for(int i=h[u];~i;i=ne[i])
	{
		int j=e[i];
		if(!st[j])
		{
			tarjan(j);
			p[j]=u;
		}
	}
	
	for(auto item:query[u])
	{
		int y=item.first,id=item.second;
		if(st[y]==2)
		{
			int anc=find(y);
			res[id]=dist[u]+dist[y]-2*dist[anc];
		}
	}
	st[u]=2;
}

int main()
{
	memset(h,-1,sizeof h);
	scanf("%d",&n);
	for(int i=0;i<n-1;i++)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c),add(b,a,c);
	}
	
	scanf("%d",&m);
	for(int i=0;i<m;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		if(a!=b)
		{
			query[a].push_back({b,i});
			query[b].push_back({a,i});
		}
	}
	
	for(int i=0;i<=n;i++)
		p[i]=i;
	
	dfs(1,-1);
	tarjan(1);
	
	for(int i=0;i<m;i++)
		printf("%d\n",res[i]);
}

G-爱玩游戏的阿华

        观察发现加体力的道具收益和休息是一样的,加属性的道具收益不如训练,增加训练效果的道具收益和训练一样。
        得到有体力就训练,没体力就休息的决策,于是回合数为偶数时属性值直接(n/2)*15
        考虑回合数为奇数的情况,训练休息轮流的策略会导致一个回合没有收益,考虑通过比赛和道具来获得这一个回合的收益。
        首先需要通过一个加 50 的体力药或者两个加的小体力药来获得比赛消耗的体力。相当于消耗一个回合但不消耗体力,获得 50金币,将多余的一个回合转化为了商店币。
        然后考虑通过商店币能获得的属性, 50 商店币能转化为一次加 6 的属性书或者两次加 3 的小属性书。
        所以 n 为奇数时被分为:浪费一个回合,属性多加 3,属性多加 6,属性多加 7,一共四种情况,分别计算出对应的概率即可。

#include <bits/stdc++.h>

using namespace std;

const int mod=1e9+7;

int T,n,P;

inline int qpow(int x,int y){
    int ans=1;
    while(y){
        if(y&1)ans=1ll*ans*x%mod;
        x=1ll*x*x%mod;
        y>>=1;
    }
    return ans;
}

int main(){
	// freopen("data1.in","r",stdin);
	// freopen("data1.out","w",stdout);
    ios::sync_with_stdio(false);
    cin>>T;
    while(T--){
        cin>>n>>P;
        if(!n){
            cout<<"0\n";
            continue;
        }
        if(n<6){
            cout<<(n/2+1)*15<<'\n';
            continue;
        }
        if(n&1){
            int t=n/6;
            int P1=(1-qpow(1-P+mod,t)+mod)%mod;//一种道具一个以��?
            int P2=(1-(qpow(1-P+mod,t)+1ll*qpow(1-P+mod,t-1)*P%mod*t%mod)%mod+mod)%mod;//一种道具两个以��?
            int PP=(1-1ll*(1-P1+mod)*(1-P2+mod)%mod+mod)%mod;//有体力药
            int p1=1ll*PP*P1%mod;
            int p2=1ll*PP*qpow(1-P+mod,t)%mod*P2%mod;
            int p3=1ll*PP*qpow(1-P+mod,t)%mod*(1ll*qpow(1-P+mod,t-1)*P%mod*t%mod)%mod;
            int p4=(1-((p1+p2)%mod+p3)%mod+mod)%mod;
            cout<<(((15ll*(n/2)%mod+22)*p1%mod
                   +(15ll*(n/2)%mod+21)*p2%mod)%mod
                  +((15ll*(n/2)%mod+18)*p3%mod
                   +(15ll*(n/2)%mod+15)*p4%mod)%mod)%mod<<'\n';
        }else{
            cout<<(15ll*(n/2)%mod+15)%mod<<'\n';
        }
    }
    return 0;
}

H-循环同构

        求出字符串的最小表示法,然后两个串如果循环同构那么两个字符串相等。利用hash判断即可。利用后缀自动机或者后缀数组之类的做法也可以通过本题。

#include<bits/stdc++.h>
using namespace std;
const int Mod = 666623333;
int T, n, m, Q, a[100005];
char s[200005];
int main(){
	scanf("%d", &T);
	while (T--){
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; i++){
			scanf("%s", s + 1);
			for (int j = m + 1; j <= m + m; j++)
				s[j] = s[j - m];
			int I = 1, J = 2;
			while (J <= m){
				ok:;
				for (int k = 0; k < m; k++){
					if (s[I + k] < s[J + k]){
						J += k;
						break;
					}
					if (s[I + k] > s[J + k]){
						int tmp = I;
						I = J;
						J = max(J, tmp + k);
						break;	
					}
				}
				J++;
			}
			a[i] = 0;
			for (int j = 0; j < m; j++)
				a[i] = (31ll * a[i] + s[I + j] - 'a' + 1) % Mod;
		}
		scanf("%d", &Q);
		while (Q--){
			int x, y;
			scanf("%d%d", &x, &y);
			if (a[x] == a[y])
				puts("Yes");
			else
				puts("No");
		}
	}
	return 0;
}

I-Coin

        考虑建立最大流模型:
        源点 S 向每个人连 1 的流量。按时间顺序拆点,每个人拆成 M 个点,M 个点之间连 a_i 的流量,对每次操作给定的 (A,B),在对应时间点上连双向的的流量(正向边流量为1,反向边流量也为1),对于 K 个朋友点,它们的最后一个时间的点向 Ta_{b_i} 流量的边。这样建图,点数和边数都为 O(N*M),现在考虑对上面的最大流进行优化。可以发现,拆出来的很多点是用不上的。所以没必要每个点都拆成 M 个点。每个人只要把选中自己的那个时间点拓展出来就行。换句话说,刚开始每个人没有点,对于每个操作,只要把选中的那两个人多扩展出一个点即可。这样建图后点数为 O(M), 边数为 O(M+K)

        时间复杂度不超过 O(N*M)

#include <bits/stdc++.h>
#define N 30000
#define S (N*2+1)
#define T (N*2+2)
#define INF 0x7FFFFFFF
#define rep(i, l, r) for (int i = (l); i <= (r); ++(i))
#define cmax(a,b) a=max(a,b)
#define cmin(a,b) a=min(a,b)
using namespace std;

inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
	return x * f;
}

struct edge {int nex, t, w;} e[500010];
int h[N*2 + 10], tot, d[N*2 + 10], q[N*2 + 10], qn, c[N*2 + 10];
int a[N + 5], p[N + 5], pn;
inline void Link(int x, int y, int w, int W = 0) {
	e[++tot] = (edge) {h[x], y, w}; h[x] = tot;
	e[++tot] = (edge) {h[y], x, W}; h[y] = tot;
}
bool bfs() {
	memset(d, 0, sizeof(d)); int i, j;
	for (d[q[i = qn = 0] = S] = 1; i <= qn; ++i)for (j = c[q[i]] = h[q[i]]; j; j = e[j].nex)
			if (e[j].w && !d[e[j].t])d[q[++qn] = e[j].t] = d[q[i]] + 1;
	return d[T];
}
int dfs(int x, int r) {
	if (x == T)return r;
	int k, u = 0;
	for (int&i = c[x]; i; i = e[i].nex)if (e[i].w && d[e[i].t] == d[x] + 1) {
			k = dfs(e[i].t, r - u < e[i].w ? r - u : e[i].w);
			u += k; e[i].w -= k; e[i ^ 1].w += k;
			if (u == r)return u;
		}
	return d[x] = 0, u;
}

void solve(){
	int n = read(), m = read(), k = read();
	rep(i,1,n)a[i] = read();
	memset(p, pn = 0, sizeof(p));
	memset(h, 0, sizeof(h)); tot = 1;
	rep(i,1,m) {
		int x = read(), y = read();
		p[x] ? Link(p[x], ++pn, a[x]) : Link(S, ++pn, 1); p[x] = pn;
		p[y] ? Link(p[y], ++pn, a[y]) : Link(S, ++pn, 1); p[y] = pn;
		Link(p[x], p[y], 1, 1);
	}
	for (int i = 1; i <= k; ++i) {
		int x = read();
		if (!p[x])Link(S, T, 1);
		else Link(p[x], T, a[x]);
	}
	int x;
	for (x = 0; bfs();)x += dfs(S, INF);
	printf("%d\n", x);
}

int main() {
	int t = read();
	while(t--)solve();
}

J-Huahua's internship

        根据题目的要求。我们先画一颗树来实验一下。

        

        因为题目要求染色的点的数量最多,所以我们考虑全部染成同一个颜色。但是,题目要求红色和蓝色都要出现,所以便有了下一张图。

        

        但这样打了绿色圆圈的边的两边就是不一样的颜色了,因此,我们必须不给 4 号点染色,即:

        其余情况可以自己推一下。

        不难发现,我们只能将一个结点不染色,然后将其删去,其余的连通块内部染成同一种颜色。我们可以枚举这个节点,然后 0-1 背包即可。

  #include<bits/stdc++.h>
  using namespace std;
  int n,cnt,tot,h[5005],w[5005],c[5005];
  bitset<5005>ans,dp;//0-1背包
  struct edge{
      int v,nxt;
  }e[10005];
  void adde(int u,int v){
      e[++cnt].nxt=h[u];
      h[u]=cnt;
      e[cnt].v=v;
  }//链式前向星
  void dfs(int x,int fa){
      w[x]++;
      for(int i=h[x];i;i=e[i].nxt){
          if(e[i].v==fa)continue;
          dfs(e[i].v,x);
          w[x]+=w[e[i].v];
      }
  }//求以1为根时分别以每个点为根的子树的节点数
  void dfs1(int x,int fa){
      int tot=0,sum=0;
      for(int i=h[x];i;i=e[i].nxt){
          if(e[i].v==fa)continue;
          c[++tot]=w[e[i].v];
          sum+=w[e[i].v];
      }
      c[++tot]=n-sum-1;
      dp.reset();
      dp[0]=1;
      for(int i=1;i<=tot;i++){
          for(int j=n;j>=c[i];j--){
              if(dp[j-c[i]])dp[j]=1;
          }
      }
      ans|=dp;//0-1背包
      for(int i=h[x];i;i=e[i].nxt){
          if(e[i].v==fa)continue;
          dfs1(e[i].v,x);
      }
  }
  int main()
  {
      scanf("%d",&n);
      for(int i=2,a,b;i<=n;i++)scanf("%d%d",&a,&b),adde(a,b),adde(b,a);
      dfs(1,0);
      dfs1(1,0);
      tot=0;
      for(int i=1;i<n-1;i++)if(ans[i])tot++;//一共有多少解
      printf("%d\n",tot);
      for(int i=1;i<n-1;i++)if(ans[i])printf("%d %d\n",i,n-i-1);//输出
      return 0;
  }

K-阿华的断言(签到题)

        鸽巢原理的应用,至少有一个堆中有 \left \lfloor \frac{m-1}{n} \right \rfloor+1 个元素。
        判断 d 和 \left \lfloor \frac{m-1}{n} \right \rfloor+1 的大小关系即可。

#include <bits/stdc++.h>
 
using namespace std;
 
 
int main() {
	cin.tie(nullptr)->sync_with_stdio(0);
 
	int T;
	cin >> T;
 
	while (T--) {
		int n , m , x;
		cin >> n >> m >> x;
 
		int d = (m - 1) / n + 1;
		if (x <= d) {
			cout << "Yes\n";
		} else {
			cout << "No\n";
		}
	}
	return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Double.Qing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值