今天题目挺难的,第二题有坑,题目中输入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;
}