2021-07-16 集训题解
Sample
Description
有若干次查询,每次给出 \(n\),构造出一个 \(p_{1,2,...,n}\) 使得:
求:
Solution
md,学了跟没学一个样(那一天,人们又回想起被支配树支配的恐惧
考虑使用拉格朗日乘数法,那么就是求:
然后你求偏导就可以得到:
然后你发现你可以解出 \(p_i=\frac{1}{2}-\frac{\lambda}{2i}\),但是你考虑到如果你直接二分 \(\lambda\) 的话可能存在 \(p_i<0\),那么你就需要把前面一个前缀 \(p_i\) 都变为 \(0\),你就二分这个就好了。
确定了 \(\lambda\) 之后求答案就很简单了。
Code
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define int long long
#define MAXN 1000005
#define eps 1e-9
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> inline void chkmax (T &a,T b){a = max (a,b);}
template <typename T> inline void chkmin (T &a,T b){a = min (a,b);}
int n;
double pre[MAXN];
int getpre (int x){return x * (x + 1) / 2;}
double getsum (int l,int r,double num){
return (getpre(r) - getpre(l)) / 4.0 - num * num * (pre[r] - pre[l]) / 4;
}
signed main(){
freopen ("sample.in","r",stdin);
freopen ("sample.out","w",stdout);
n = 1e6;for (Int i = 1;i <= n;++ i) pre[i] = pre[i - 1] + 1.0 / i;
int T;read (T);
while (T --> 0){
read (n);
if (n == 1){printf ("%.10f\n",0.0);continue;}
int l = 0,r = n,ans = 0;
while (l <= r){
int mid = l + r >> 1;double num = (n - mid - 2) / (pre[n] - pre[mid]);
if (num <= mid + 1) ans = mid,r = mid - 1;
else l = mid + 1;
}
double num = (n - ans - 2) / (pre[n] - pre[ans]);
printf ("%.10f\n",2 * getsum (ans,n,num));
}
return 0;
}
Det
Description
保证 \(m\le n+300\)
Solution
考虑到一个矩阵的二维差分数组的行列式与原矩阵行列式相同,那么每次 \([l,r]\) 操作就相当于 \(l\to r+1\) 有一条边,问最后生成树的个数。
考虑到 \(m\le n+300\),也就是说我们只要能把需要求的范围缩到 \(m-n\) ,就可以用矩阵树定理求解。
可以想到,度数为 \(1\) 的点肯定可以确定,考虑度数为 \(2\) 的点,它们肯定是串成一个链,考虑在链两端连上虚边,如果选,则全选,否则选一条不选,那么也就是说选的贡献为 \(1\),不选的贡献为 \(len\),考虑把答案先除以 \(len\),那么就是选会贡献 \(\frac{1}{len}\)。具体实现可以使用 tarjan 来实现。
Code
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define mod 998244353
#define MAXM 500005
#define MAXN 605
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> void chkmax (T &a,T b){a = max (a,b);}
template <typename T> void chkmin (T &a,T b){a = min (a,b);}
int n,m,tot,a[MAXN][MAXN];
int mul (int a,int b){return 1ll * a * b % mod;}
int dec (int a,int b){return a >= b ? a - b : a + mod - b;}
int add (int a,int b){return a + b >= mod ? a + b - mod : a + b;}
int qkpow (int a,int b){
int res = 1;for (;b;b >>= 1,a = mul (a,a)) if (b & 1) res = mul (res,a);
return res;
}
int inv (int x){return qkpow (x,mod - 2);}
void Add (int &a,int b){a = add (a,b);}
void Sub (int &a,int b){a = dec (a,b);}
void link (int u,int v,int w){
Add (a[u][u],w),Add (a[v][v],w),
Sub (a[u][v],w),Sub (a[v][u],w);
}
int getdet (int N){
int ans = 1;
for (Int i = 1;i <= N;++ i){
int pos = i;
for (Int j = i + 1;j <= N;++ j) if (a[j][i]){pos = j;break;}
if (pos ^ i) swap (a[i],a[pos]),ans = mod - ans;
for (Int j = i + 1,iv = inv (a[i][i]);j <= N;++ j){
int del = mul (a[j][i],iv);
for (Int k = i;k <= N;++ k) Sub (a[j][k],mul (del,a[i][k]));
}
ans = mul (ans,a[i][i]);
}
return ans;
}
stack <int> S;
vector <int> g[MAXM],sta[MAXM];
int idx,ans = 1,num[MAXM],dfn[MAXM],low[MAXM],bel[MAXM];
void Tarjan (int u,int fa){
bool flg = 0;
dfn[u] = low[u] = ++ idx,S.push (u);
for (Int v : g[u]){
if (!dfn[v]) Tarjan (v,u),chkmin (low[u],low[v]);
else{
if (v == fa && !flg) flg = 1;
else chkmin (low[u],dfn[v]);
}
}
if (low[u] == dfn[u]){
++ tot;
while (1){
int now = S.top();S.pop ();
sta[bel[now] = tot].push_back (now);
if (now == u) break;
}
}
}
bool tag[MAXM];
void ins (int r,int now,int lst,int len){
if (g[now].size() > 2){
int c = num[now];
if (r < c) ans = mul (ans,len),link (r,c,inv (len));
else if (r == c && !tag[lst]) ans = mul (ans,len);
return ;
}
ins (r,g[now][0] == lst ? g[now][1] : g[now][0],now,len + 1);
}
void solveit (int e){
if (sta[e].size() == 1) return ;
int s = 0;for (Int x : sta[e]) if (g[x].size() > 2) num[x] = (++ s);
if (s == 0) return ans = mul (ans,sta[e].size()),void ();
for (Int i = 1;i <= s;++ i) for (Int j = 1;j <= s;++ j) a[i][j] = 0;
for (Int u : sta[e]) if (g[u].size() > 2) for (Int v : g[u]) ins (num[u],v,u,1),tag[v] = 1;
ans = mul (ans,getdet (s - 1));
}
int su[MAXM],sv[MAXM];
signed main(){
freopen ("det.in","r",stdin);
freopen ("det.out","w",stdout);
read (n,m);
for (Int i = 1;i <= m;++ i){
int l,r;read (l,r),su[i] = l,sv[i] = r + 1;
g[l].push_back (r + 1),g[r + 1].push_back (l);
}
Tarjan (1,0);
for (Int i = 1;i <= n + 1;++ i) if (!dfn[i]) return puts ("0") & 0;
for (Int i = 1;i <= n + 1;++ i) g[i].clear ();
for (Int i = 1;i <= m;++ i) if (bel[su[i]] == bel[sv[i]]) g[su[i]].push_back (sv[i]),g[sv[i]].push_back (su[i]);
for (Int i = 1;i <= tot;++ i) solveit (i);
write (ans),putchar ('\n');
return 0;
}
Game
Description
小明设计了一款新游戏。有一张 \(n\) 行 \(m\) 列的网格,有的格子上有一张卡片。每张卡片分为正反两面,每张卡片可能是正面或反面朝上。你每次可以选择一行,并将这一行的所有卡片翻面。小明将游戏的胜利目标定为了“每列最多有一张卡片朝上”,并且设计出了一些关卡(每个关卡都是可以达成胜利条件的)。
小君正在测试这些关卡,然而小君视力不太好,因此把胜利条件看成了“每列至少有一张卡片朝上”。他想知道他能否达成这个(错误的)胜利条件。如果可以,请输出一种方案。
Solution
首先可以对原矩阵进行处理,把所有只有一个格子的列都删掉,保证每列至少两个格子。
考虑转换问题,你发现如果可以实现每列最多只有一张卡片朝上的话就可以全员反转来实现,你发现这个可以 2-sat 实现,具体来说可以前后缀优化建图。
Code
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define MAXM 2000005
#define MAXN 2000005
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> void chkmax (T &a,T b){a = max (a,b);}
template <typename T> void chkmin (T &a,T b){a = min (a,b);}
int n,m;
vector <int> fuc[MAXN],ld[MAXN],g[MAXM],pre[MAXN],suf[MAXN];
void link (int u,int v){
g[u].push_back (v);
}
stack <int> S;
bool vis[MAXN];
int ind,cnt,bel[MAXN],dfn[MAXN],low[MAXN];
void tarjan (int u){
dfn[u] = low[u] = ++ ind,vis[u] = 1,S.push (u);
for (Int v : g[u])
if (!dfn[v]) tarjan (v),chkmin (low[u],low[v]);
else if (vis[v]) chkmin (low[u],dfn[v]);
if (low[u] == dfn[u]){
++ cnt;
while (1){
int now = S.top();S.pop ();
bel[now] = cnt,vis[now] = 0;
if (now == u) break;
}
}
}
int getid (int x,int k){return k * n + x;}
int mst[MAXN];
bool forg[MAXN];
void solve (int now){
if (now > n) return ;
for (Int x : fuc[now]){
int tot1 = 0,tot2 = 0,pos = 0;
for (Int i = 0;i < ld[x].size();++ i) if (mst[abs(ld[x][i])]) tot2 += (mst[abs(ld[x][i])] * ld[x][i] > 0);else tot1 ++,pos = i;
if (tot1 == 1 && tot2 == 0) mst[abs(ld[x][pos])] = (ld[x][pos] > 0 ? 1 : -1),forg[x] = 1;
if (tot2 == 0 && tot1 == 0){puts ("No");exit (0);}
}
solve (now + 1);
}
bool rev[MAXN];
signed main(){
freopen ("game.in","r",stdin);
freopen ("game.out","w",stdout);
read (n,m);int tot = n * 2;
for (Int i = 1;i <= m;++ i){
int u;read (u),fuc[u].push_back (i),ld[i].resize (u),pre[i].resize (u),suf[i].resize (u);
if (u == 0) return puts ("No") & 0;
for (Int j = 0;j < u;++ j) read (ld[i][j]),pre[i][j] = ++ tot,suf[i][j] = ++ tot;
}
solve (1);
for (Int x = 1;x <= m;++ x) if (!forg[x]){
for (Int i = 0;i < ld[x].size();++ i) if (!mst[abs(ld[x][i])]){
if (i) link (getid(abs(ld[x][i]),0 ^ (ld[x][i] < 0)),pre[x][i - 1]),link (pre[x][i],pre[x][i - 1]);
if (i != ld[x].size() - 1) link (getid (abs(ld[x][i]),0 ^ (ld[x][i] < 0)),suf[x][i + 1]),link (suf[x][i],suf[x][i + 1]);
link (pre[x][i],getid (abs(ld[x][i]),1 ^ (ld[x][i] < 0))),link (suf[x][i],getid (abs (ld[x][i]),1 ^ (ld[x][i] < 0)));
}
}
for (Int i = 1;i <= tot;++ i) if (!dfn[i]) tarjan (i);
vector <int> opt;
for (Int i = 1;i <= n;++ i)
if (!mst[i]){
if (bel[getid(i,0)] == bel[getid(i,1)]) return puts ("No") & 0;
else if (bel[getid(i,0)] < bel[getid(i,1)]) opt.push_back (i);
}
else if (mst[i] == -1) opt.push_back (i);
puts ("Yes"),write (opt.size()),putchar ('\n');
for (Int x : opt) write (x),putchar ('\n');
return 0;
}