T1
solution
不会?完全没思路?那就来看看性质吧
如果(x o y) 合法,那么(y o x) 合法(自反性)
如果(x o y,y o z) 合法,那么(x o z) 合法(传递性)
由此可知若将可以通过合法序列到达的点连边,形成的图一定是一个由若干团拼成的,而我们只需要找出这些团最后统计答案即可
考虑合法的括号序列是如何生成的?
- 空串合法
- (A)合法( o) ((A))合法
- (A,B)合法( o) (AB)合法
由此我们可以以(())为基础,通过和合法串的拼接或在外面加括号实现任意合法括号序列的生成
(())如何出现?
如图,两条黑边的括号类型是相同的,此时(x o y(y o x))就是(())
考虑合法串拼接,即(x o y o z) (其中(y o z) 合法)
考虑在外面加括号,即(a o x o y o b) (其中(a o x,b o y) 的边都是同类型的左括号)
在这两种生成方式中,不难发现和具体(x)如何到(y)没有关系,只关心是否可达
意即可以将(x,y)合并当作一个点处理
根据上面的思考不难看出(x,y)的合并可能会引出新的合并,而我们只需不断地这样合并直到没有合法点对,此时算法结束,所有点所在的团的编号已被求出
具体实现可以采用哈希表映射存边(括号类型是原象,边起点是象),用并查集模拟合并,用队列处理一次合并引发出的多次合并。另外,为保证复杂度,需使用启发式合并。
time complexity
(mathcal O(mlog m))
code
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
typedef pair<int,int> pii;
typedef long long ll;
unordered_map<int,int>mp[N];
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
int fa[N],sz[N],n,m,k,cnt[N];
int fd(int x){return fa[x]==x?x:fa[x]=fd(fa[x]);}
queue<pii>q;
inline void mge(int x,int y)
{
x=fd(x),y=fd(y);
if(x==y)return;
if(sz[x]<sz[y])swap(x,y);fa[y]=x;
for(auto t=mp[y].begin();t!=mp[y].end();t++)
{
if(!mp[x].count(t->first))mp[x][t->first]=t->second,++sz[x];
else q.push(make_pair(t->second,mp[x][t->first]));
}
}
int main()
{
n=read(),m=read(),k=read();
for(int i=1,u,v,w;i<=m;++i)
{
u=read(),v=read(),w=read();
if(!mp[v].count(w))mp[v][w]=u,++sz[v];
else q.push(make_pair(mp[v][w],u));
}
for(int i=1;i<=n;++i)fa[i]=i;
while(!q.empty())
{
auto t=q.front();q.pop();
mge(t.first,t.second);
}ll ans=0;
for(int i=1;i<=n;++i)++cnt[fd(i)];
for(int i=1;i<=n;++i)ans+=1ll*cnt[i]*(cnt[i]-1)/2;
printf("%lld
",ans);
return 0;
}
T2
solution
对于表达式(E) ,不妨建出其表达式树(为二叉树),它的特点如下
- 叶子节点表示操作数
- 非叶子节点代表一次运算,即对左右儿子进行一次(>) 或(<) 运算
设其根为(rt)
不难发现数组的每一位都是相对独立的,因此只用考虑(m)个数的情况,设它们为(a_1,a_2,cdots ,a_m)
考虑到(m)很小,同时最终答案也必然在(m)个数中产生,我们不妨考虑每个数作为答案出现多少次,然后加和
直接做不好做,但是我们发现,当枚举到(i) 时,(a_i) 成为最终答案的次数仅仅和(a_1,a_2,cdots,a_m) 中哪些比它大,哪些比它小,哪些和它相等有关,而和具体相差多少无关。于是我们可以把其中比它大的看作(2), 和它相等的看作(1), 比它小的看作(0), 然后我们就可以在表达式树上(dp)了
记(f_{u,k})表示节点(u)的计算结果为(k)的方案数,考虑转移
- 当(u)是叶子节点时,将对应的状态置为(1)即可
- 当(u)是非叶子节点时,设其左儿子为(l),右儿子为(r)
如果(u)对应的操作符为(>),那么
若其操作符为(<),那么
若为(?),考虑加法原理,只需把以上两式相加即可
最终答案就是(f_{rt,1})
时间复杂度(mathcal O(nm(m+|E|)))
这样下来复杂度仍然无法承受,考虑优化
能否不枚举(i)?答案是肯定的,因为每一次都是(m)个数,而每个数都在(0)和(2)之间,于是我们可以先预处理出所有情况,然后记下答案。每次先将(a)数组排序,从小到大地考虑即可
时间复杂度(mathcal O(3^m|E|+n(mlog_2m+m)))
然而预处理的复杂度仍然难以承受,考虑继续优化
可以将小于(a_i)的看作(0),将大于等于(a_i)的看作(1), 这样可以求出结果大于等于(a_i)的方案数,然后再差分一下就可以得到答案了
时间复杂度(mathcal O(2^m|E|+n(mlog_2m+m)))
可以承受
code
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N=5e4+5,K=12,mod=1e9+7;
int n,m,A[K][N],rt,tot,lb[N],pos[N],sta[N],top;
int ls[N],rs[N],ans,d[K];pii B[K];
char s[N];
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
void build(int&p,int l,int r)
{
if(pos[r]==l)return build(p,l+1,r-1);
p=++tot;int t=0;
if(l==r){lb[p]=s[l]^48;return;}
for(int i=r;i>=l;--i)
{
if(pos[i]){i=pos[i];continue;}
if(s[i]=='<'||s[i]=='>'||s[i]=='?'){t=i;break;}
}lb[p]=s[t];
build(ls[p],l,t-1),build(rs[p],t+1,r);
}
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
int f[N][2],ret[1200];
inline void upmn(int u)
{
for(int i=0;i<=1;++i)
for(int j=0;j<=1;++j)
{
int&d=f[u][min(i,j)];
d=add(d,1ll*f[ls[u]][i]*f[rs[u]][j]%mod);
}
}
inline void upmx(int u)
{
for(int i=0;i<=1;++i)
for(int j=0;j<=1;++j)
{
int&d=f[u][max(i,j)];
d=add(d,1ll*f[ls[u]][i]*f[rs[u]][j]%mod);
}
}
void dp(int u)
{
f[u][0]=f[u][1]=0;
if(!ls[u]){f[u][d[lb[u]]]=1;return;}
dp(ls[u]),dp(rs[u]);
if(lb[u]!='<')upmx(u);
if(lb[u]!='>')upmn(u);
}
inline int gans(int w)
{
if(ret[w]>=0)return ret[w];
for(int i=0;i<m;++i)d[i]=(w>>i)&1;
dp(rt);return ret[w]=f[rt][1];
}
int main()
{
n=read(),m=read();
for(int i=0;i<m;++i)
for(int j=1;j<=n;++j)
A[i][j]=read();
scanf("%s",s+1);
int len=strlen(s+1);
for(int i=1;i<=len;++i)
{
if(s[i]=='(')sta[++top]=i;
else if(s[i]==')')pos[i]=sta[top--];
}
build(rt,1,len);
int st=1<<m;fill(ret,ret+st,-1);
#define x first
#define y second
for(int i=1;i<=n;++i)
{
for(int j=0;j<m;++j)
B[j]=make_pair(A[j][i],j);
sort(B,B+m);
int p=st-1,pre=gans(p);
for(int j=0;j<m;)
{
int val=B[j].x;
p^=1<<B[j].y,++j;
while(B[j].x==B[j-1].x)p^=1<<B[j].y,++j;
int now=gans(p);
ans=add(ans,1ll*val*dec(pre,now)%mod);
pre=now;
}
}
#undef x
#undef y
printf("%d
",ans);
return 0;
}
T3
solution
先定义数列(f)
然后有结论:数列(f)在模(m)意义下是纯循环的,且最小正周期是(mathcal O(n))的(常数也不大)
证明?自己写程序吧
不难证明原题中的(F)可以表示为
依题意,即要求最小的(n)满足((a)或(b)等于(0)的情况先特判掉)
即
看上去问题似乎已经解决了,只需预处理所有的(frac {f_{n+1}}{f_n}mod m),然后询问时查表即可
然而注意到(b,f_n)可能和(m)并不互质,这导致其在模(m)意义下并不存在逆元。怎么办?
引理:
不妨令(g=gcd(a,m-b,m),a'=frac ag,b'=frac bg,m'=frac mg),由此
考察如上文氏图
相交的部分代表公共的因子
由于
所以
同理得
由于
(辗转相除求最大公约数的方法可以证明此结论)
因此
而倘若(m''mid f_n),则(m''mid f_{n+1}),与此二者互质矛盾,反之可得相同结论,因此
所以
因此可得
此时的除法显然是有意义的
因此,我们只需采用(map) 映射,将四元组((m',p,q,frac{frac{f_{n+1}}p}{frac{f_n}q}mod frac{m'}{pq})) 映射到对应的最小的(n)即可
预处理时枚举(m') (即(m) 的约数),然后花费(mathcal O(m')) 的时间处理出(m') 处的上述四元组
询问时按照上述过程运算,然后查询即可
time complexity
预处理复杂度:(mathcal O(sigma(m)log_2(sigma(m)))=mathcal O(mln m(ln m+lnln m))) 其中(sigma(m)) 为因数和
查询复杂度:(mathcal O(nlog m))
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
struct node{int p,q,k;};
inline bool operator<(const node&x,const node&y)
{
return x.p!=y.p?x.p<y.p:(x.q!=y.q?x.q<y.q:x.k<y.k);
}
map<node,int>mp[N];
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
int gcd(int x,int y){return y?gcd(y,x%y):x;}
void exgcd(int&x,int&y,int a,int b)
{
if(!b){x=1,y=0;return;}
exgcd(x,y,b,a%b);
int t=x;x=y,y=t-a/b*y;
}
inline int inv(int a,int mod)
{
int x,y;exgcd(x,y,a,mod);
return (x%mod+mod)%mod;
}
inline void pre()
{
for(int _=2;_<=m;++_)
{
if(m%_)continue;
int x=1,y=0;
for(int cnt=0;;++cnt)
{
if(x&&y)
{
int d1=gcd(_,x),d2=gcd(_,y),pm=_/d1/d2;
int k=1ll*(y/d2)*inv(x/d1,pm)%pm;
if(!mp[_].count({d1,d2,k}))mp[_][{d1,d2,k}]=cnt;
}
int t=x;x=y,y=(y+t)%_;
if(x==1&&y==0)break;
}
}
}
int main()
{
n=read(),m=read();pre();
while(n--)
{
int a=read(),b=read();
if(!a){puts("0");continue;}
if(!b){puts("1");continue;}b=(m-b)%m;
int g=gcd(gcd(a,b),m),m1=m/g;a/=g,b/=g;
int p=gcd(a,m1),q=gcd(b,m1),m2=m1/p/q;
a/=p,b/=q;
int k=1ll*a*inv(b,m2)%m2;
if(!mp[m1].count({q,p,k}))puts("-1");
else printf("%d
",mp[m1][{q,p,k}]);
}
return 0;
}