A-Fibonacci
递推式和矩阵乘法
斐波那契数列有递推公式
我们可以把这个计算过程抽象成一个矩阵运算的过程。
那么对于第 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 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-传送
设 元形式幂级数 。
其中 表示向量
作为技能出现的次数。
我们希望求出 表示:
那么其中 的系数
则表示利用技能集合凑出向量
的方案数。
对于存在障碍物的方案数求解我们只需要 的容斥即可,设
表示到达第 个点的方案数。
这里 表示第
个关键点的坐标向量,
表示组合出向量
的方案数,定义
维向量的
小于
为每一维均小于,同时
为每一维相减。
现在我们只需要解决一个问题就是 元形式幂级数的求逆。
首先解决 元多项式乘法,对于常见的
比较小的情况可以将每一维的值域扩张成两倍,然后直接做卷积并舍弃进位的情况即可。
对于 较大的情况,我们多引入一维
, 并引入占位函数
,将函数表示成
。
多引入一个元可以增强对卷积的限制,我们原先的问题在于我们将向量重标号后,进行加法之后的标号会产生进位并对答案产生影响,在引入 之后还需要满足 ,注意到有
,所以
合法的集合很小。于是我们只需要计算
在
意义下的多项式乘法,也就是循环卷积。
由于 ,那么就可以对每一维度做长度为
的 DFT,然后对于
暴力做卷积。对于求逆,只需像一元多项式一样利用牛顿迭代求解即可。求导也是一致的。
时间复杂度为
#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-生成树问题
令第 种权值的边有
条。首先考虑二分答案,二分答案后则对于每种权值
,限制
不能取超过 条边。不妨将问题转化为,限制每种权值的边不能删去超过
条边,并且要求删去选出来的边之后全图联通。不难发现,存在一棵生成树满足条件(权值
出现次数不超过
),等价于在新的限制下,能删去的边的总数等于
。另一方面,删除边集小于等于
和删边后联通均为拟阵,即可使用拟阵交解决问题。复杂度
。
#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(签到题)
很明显题目是要求 ,但是出题人很狗,把暴力做法卡掉了,所以套个快速幂板子就能过啦。
#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-爱玩游戏的阿华
观察发现加体力的道具收益和休息是一样的,加属性的道具收益不如训练,增加训练效果的道具收益和训练一样。
得到有体力就训练,没体力就休息的决策,于是回合数为偶数时属性值直接。
考虑回合数为奇数的情况,训练休息轮流的策略会导致一个回合没有收益,考虑通过比赛和道具来获得这一个回合的收益。
首先需要通过一个加 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
考虑建立最大流模型:
源点 向每个人连 1 的流量。按时间顺序拆点,每个人拆成
个点,
个点之间连
的流量,对每次操作给定的
,在对应时间点上连双向的的流量(正向边流量为1,反向边流量也为1),对于
个朋友点,它们的最后一个时间的点向
连
流量的边。这样建图,点数和边数都为
,现在考虑对上面的最大流进行优化。可以发现,拆出来的很多点是用不上的。所以没必要每个点都拆成
个点。每个人只要把选中自己的那个时间点拓展出来就行。换句话说,刚开始每个人没有点,对于每个操作,只要把选中的那两个人多扩展出一个点即可。这样建图后点数为
, 边数为
。
时间复杂度不超过 。
#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-阿华的断言(签到题)
鸽巢原理的应用,至少有一个堆中有 个元素。
判断 d 和 的大小关系即可。
#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;
}