data track capacitor 10.3模擬題 題解總結
今天題目挺難的,第二題有坑,題目中輸入n,m,根所屬的節點後,還有個E (= n-m)的輸入,導致全場第二題爆零。
就得了第一題100分,第三題暴力深搜都錯了。。。我實在太弱了。。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
第一題。
題意中電容公式其實就是咱的電阻串並聯的公式,只是反著來(串是並的效果,並是串的效果)。
實際上如果當前拼成的是 c/d 那麼並聯相當於變成(c+d)/d,串聯相當於變成(c+d) /c。
相當於一個輾轉相除法,就看除了多少下,多少下就是多少個電阻。
(dalao們不需要的)程式碼:
#include<bits/stdc++.h> using namespace std; #define ll long long const int INF = 999999999; int T; ll a,b; ll ans = 0; ll Gcd(ll a,ll b) { return b == 0 ? a : Gcd(b,a%b); } int main() { // freopen("capacitor.in","r",stdin); // freopen("capacitor.out","w",stdout); ios::sync_with_stdio(false); cin.tie(0); cin >> T; while(T--) { cin >> a >> b; ans = 0; ll gcd = Gcd(a,b); a /= gcd,b /= gcd; while(a != 0 && b != 0) { if(a > b) { ans += a/b; a %= b; } else { swap(a,b); ans += a/b; a %= b; } } cout << ans << endl; } return 0; }
題目大意
要求維護一個森林,支援連邊操作和查詢兩點 LCA 操作。
有個大坑,上面說的,除此以外還有個很陰險的坑:連邊會破壞樹的結構,會換根!
dalao看到一句LCT就A了,但咱是蒟蒻。。
於是咱用noip的 按秩合併(啟發式合併)並查集+Lca倍增 複雜度O(nlog^2)可以過。這為線上做法,要慢一點,但由於本蒟蒻考場上寫的跟這種很相似,下來根據講解稍微改了一下就過了。
另一種為離線操作,先掃一遍提問,把應該連的邊都連上,任找一個點為根建立 LCA 倍增陣列。然後從頭到尾處理每次提問:
對於操作 1,用並查集維護每個點當前所在的樹的編號,和這棵樹現在“真正”的根是誰,
對於操作 2,我們要求出當 LCA 倍增陣列是以某個點(VROOT)為根建立的時候,兩點(u, v)在以某個點(root)為根的意義下的 LCA。求出 u, v,root 兩兩之間的 LCA,找出其中在以 VROOT 為根時,深度最大的那個 LCA(討論 u,v,root 三點在以 VROOT 為根時的相對位置關係,不(hen)難證明其正確性(我不會))。
時間複雜度 O(Q log N) 要快一點。
於是 按秩合併+LCA倍增 程式碼附上:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define in(x) read(x)
#define out(x) print(x)
const int MAXN = 1e5 + 5;
int f[MAXN][21];
int dep[MAXN],rt[MAXN],root[MAXN];
int size[MAXN];
int head[MAXN*2],cnt = 0;
void read(int &x)
{
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
void print(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) print(x/10);
putchar(x%10+'0');
}
struct node
{
int next,to;
}e[MAXN*2];
void add(int u,int v)
{
e[++cnt].next = head[u],e[cnt].to = v,head[u] = cnt;
e[++cnt].next = head[v],e[cnt].to = u,head[v] = cnt;
}
int n,m,q,E;
void rebuild(int x,int fr)
{
for(int i = 1;i <= 20;i++) f[x][i] = f[f[x][i-1]][i-1];
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].to;
if(to == fr) continue;
dep[to] = dep[x] + 1;
f[to][0] = x;
rebuild(to,x);
}
}
void dfs(int x,int fr)
{
f[x][0] = fr;
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].to;
if(to == fr) continue;
dep[to] = dep[x] + 1;
dfs(to,x);
size[x] += size[to];
}
}
int find(int x)
{
for(int i = 20;i >= 0;i--) if(f[x][i]) x = f[x][i];
return x;
}
int Lca(int x,int y)
{
if(dep[x] < dep[y]) swap(x,y);
for(int i = 20;i >= 0;i--)
if(dep[x] - (1 << i) >= dep[y])
x = f[x][i];
if(x == y) return x;
for(int i = 20;i >= 0;i--)
if(f[x][i] != f[y][i])
x = f[x][i],y = f[y][i];
return f[x][0];
}
int main()
{
in(n); in(m);
for(int i = 1;i <= m;i++) in(rt[i]);
read(E);
for(int i = 1;i <= E;i++)
{
int x,y;
in(x); in(y);
add(x,y);
}
for(int i = 1;i <= n;i++) size[i] = 1;
for(int i = 1;i <= m;i++)
{
dep[rt[i]] = 1;
dfs(rt[i],0);
}
for(int j = 1;j <= 20;j++)
for(int i = 1;i <= n;i++)
f[i][j] = f[f[i][j-1]][j-1];
for(int i = 1;i <= n;i++)
{
int o = find(i);
if(o) root[i] = o;
else root[i] = i;
}
in(q);
for(int i = 1;i <= q;i++)
{
int tmp,u,v;
in(tmp); in(u); in(v);
if(tmp == 1)
{
int x = find(u),y = find(v);
if(x == y) continue;
if(size[x] > size[y])
{
f[v][0] = u;
size[x] += size[y];
dep[v] = dep[u] + 1;
root[y] = root[x];
add(u,v);
rebuild(v,u);
}
else
{
f[u][0] = v;
size[y] += size[x];
dep[u] = dep[v] + 1;
root[y] = root[x];
add(u,v);
rebuild(u,v);
}
}
else{
if(find(u) != find(v))
{
cout << "orzorz" << endl;
continue;
}
int x = find(u);
int o = Lca(u,v),t = Lca(root[x],v);
if(dep[o] < dep[t]) o = t;
t = Lca(root[x],u);
if(dep[o] < dep[t]) o = t;
out(o);
putchar('\n');
}
}
return 0;
}
暴力大法好,蒟蒻打不了。
考試時看出是DP,於是使勁寫,,,然後沒寫出來。
我一直認為f[200][200][200]會爆空間,,,發現自己計算時多乘了個100....變成1e8的bite,,,果斷思考滾動陣列,然後就沒寫出來。。(如果哪位大佬看出來確實可以用滾動陣列(就是那個i 當前秒數)確實可以的話請評論告訴我,謝謝!)
正解就是f[i][j][k]三維。。。
dp[i][j][k]表示到第 i 秒後當前高度是 j 且匹配了 k 位的方案數。
那麼 dp[i][j][k]可以轉移:
if(s[k] == 'U'){
(dp[i + 1][j + 1][k + 1] += dp[i][j][k]) %= mod;
if(j) (dp[i + 1][j - 1][fail[k][1]] += dp[i][j][k]) %= mod;}
if(s[k] == 'D'){
(dp[i + 1][j + 1][fail[k][0]] += dp[i][j][k]) %= mod;
if(j) (dp[i + 1][j - 1][k + 1] += dp[i][j][k]) %= mod;}
特別的,我們記 dp [i][j][slen]為到第 i 秒後當前高度是 j 且匹配成功的所有方案數。
那麼 (dp[i + 1][j + 1][slen] += dp[i][j][slen]) %= mod;
if(j) (dp[i + 1][j - 1][slen] += dp[i][j][slen]) %= mod;
答案就是 dp[n][0][slen]
其中fail[k][0/1]代表第k位未成功匹配(就是那個'U'和'D'不能連成能令貓摔跤的那個連續字串)後可以從哪裡再次開始匹配。
後面一維 1 代表在'U'處沒能匹配上,0 代表在'D'處沒能匹配上。、
這個fail[k][0/1]可以kmp預處理出來,也很快。由於字串很短 直接暴力處理也行。
程式碼:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
const int MOD = 1000000007;
int ans = 0;
string s;
int n,len;
int f[MAXN][MAXN][MAXN],fail[MAXN][2],next[MAXN];
void init()
{
int j = 0;
next[0] = next[1] = 0;
for(int i = 1;i < len;i++)
{
while(j > 0 && s[i] != s[j]) j = next[j];
if(s[i] == s[j]) j++;
next[i+1] = j;
}
fail[0][s[0] == 'U'] = 1;
for(int i = 1;i <= len;i++)
{
int pos = i;
while(pos && s[pos] != 'U') pos = next[pos];
fail[i][1] = pos+1;
if(pos == 0 && s[0] == 'D') fail[i][1] = 0;
pos = i;
while(pos && s[pos] != 'D') pos = next[pos];
fail[i][0] = pos+1;
if(pos == 0 && s[0] == 'U') fail[i][0] = 0;
}
}
int main()
{
// freopen("track.in","r",stdin);
// freopen("track.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> s;
len = s.size();
if(n & 1)
{
cout << 0 << endl;
return 0;
}
init();
f[0][0][0] = 1;
for(int i = 0;i < n;i++)
{
for(int j = 0;j <= min(i,n-i);j++)
{
for(int k = 0;k < len;k++)
{
if(s[k] == 'U')
{
f[i+1][j+1][k+1] = (f[i+1][j+1][k+1] + f[i][j][k]) % MOD;
if(j) f[i+1][j-1][fail[k][0]] = (f[i+1][j-1][fail[k][0]] + f[i][j][k]) % MOD;
}
else
{
f[i+1][j+1][fail[k][1]] = (f[i+1][j+1][fail[k][1]] + f[i][j][k]) % MOD;
if(j) f[i+1][j-1][k+1] = (f[i+1][j-1][k+1] + f[i][j][k]) % MOD;
}
}
f[i+1][j+1][len] = (f[i+1][j+1][len] + f[i][j][len]) % MOD;
if(j) f[i+1][j-1][len] = (f[i+1][j-1][len] + f[i][j][len]) % MOD;
}
}
cout << f[n][0][len] % MOD;
return 0;
}