JOISC 2020 DAY2
Day 2
T1
二分图的部分分直接二分可以得到三个点,一个入度一个出度一个颜色相等。
然后你可以询问两次看颜色相等和入度是哪两个(询问答案是1),就能做了。
非二分图每次暴力看能不能加入当前集合,然后再把剩下的点查询边并且对剩下的点递归下去。
容易证明次数是 \(3n\log n + 3n + 2n + 2n\) 左右。
#include "chameleon.h"
#include<bits/stdc++.h>
using namespace std;
vector<int> st[1010];
bool in[1010];
int c[1010][1010];
map<vector<int>,int> Q;
int Tim;
int Qry(vector<int> s){
sort(s.begin(),s.end());
if(Q.find(s)!=Q.end())return Q[s];
++Tim;
return Q[s]=Query(s);
}
inline void Div(vector<int> s,int added){
if(!s.size())return ;
if(s.size()==1){
st[added].push_back(s[0]);
st[s[0]].push_back(added);
return ;
}
vector<int> Div1,Div2;
int sz = s.size();
for(int i=0;i<sz;i++){
if(i<sz/2)Div1.push_back(s[i]);
else Div2.push_back(s[i]);
}
if(Div1.size()){
Div1.push_back(added);
int q1=Div1.size()-1;
int q2=Qry(Div1);
Div1.pop_back();
if(q2<=q1){
Div(Div1,added);
}
else{
Div(Div2,added);
}
}else Div(Div2,added);
}
vector<int> ers(vector<int> s,int t){
vector<int> q;
for(size_t i=0;i<s.size();i++){
if(s[i]!=t)q.push_back(s[i]);
}return q;
}
void Solve(int N) {
for(int i=0;i<=N*2;i++){
st[i].clear();
in[i]=0;
}
int csz=0;
while(csz<N*2){
vector<int> cur;
int c = 0;
vector<int> v2;
for(int i=1;i<=N*2;i++)if(!in[i]){
cur.push_back(i);
int q=Qry(cur);
if(q<=c){
cur.pop_back();
v2.push_back(i);
}
else{
c=q;
in[i]=1;
csz++;
}
}
for(size_t i=0;i<v2.size();i++){
vector<int> v3=cur;
while(1){
int sz=v3.size();
v3.push_back(v2[i]);
int q=Qry(v3);
v3.pop_back();
if(q<=sz){
Div(v3,v2[i]);
v3=ers(v3,st[v2[i]].back());
}else break;
}
}
}
for(int i=1;i<=N*2;i++)in[i]=0;
for(int i=1;i<=N*2;i++)for(int j=1;j<=N*2;j++)c[i][j]=0;
for(int i=1;i<=N*2;i++){
if(st[i].size()>2){
assert(st[i].size()==3);
int sz=st[i].size();
int t=0;
for(int k=0;k<sz;k++)for(int k2=k+1;k2<sz;k2++){
++t;
vector<int> sq;
sq.push_back(i),sq.push_back(st[i][k]),sq.push_back(st[i][k2]);
if(t==3||Qry(sq)==1){
for(int t=1;t<=2;t++)c[i][sq[t]]=1;
goto END;
}
}
END:;
}else{
for(size_t j=0;j<st[i].size();j++){
c[i][st[i][j]]=1;
}
}
}
for(int i=1;i<=N*2;i++){
for(int j=1;j<=N*2;j++){
if(!in[i]&&!in[j]&&c[i][j]&&c[j][i]){
Answer(i,j);
in[i]=in[j]=1;
}
}
}
return ;
}
T2
启发式合并即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
const int M = 3e5+5;
typedef long long ll;
typedef pair<int,int> pi;
int sz[N],fa[N];
inline int find(int x){return fa[x]==x?fa[x]:fa[x]=find(fa[x]);}
int n,m;
vector<pi> stk;
map<int,set<int> > Eout[N];
map<int,set<int> > Ein[N];
ll ans = 0;
int Ot[N],In[N];
inline void merge(int u,int v){
int sz1=In[u]+Ot[u], sz2 = In[v]+Ot[v];
if(sz1>sz2)swap(sz1,sz2),swap(u,v);
fa[u]=v;
for(map<int,set<int> >::iterator it=Eout[u].begin();it!=Eout[u].end();it++){
for(set<int>::iterator j=it->second.begin();j!=it->second.end();j++){
int _u=*j;
int to=it->first;
Ein[to][u].erase(_u);
In[to]--;
if(Ein[to][u].size()==0){Ein[to].erase(u);}
ans-=sz[to];
stk.push_back(pi(_u,to));
}
}
for(map<int,set<int> >::iterator it=Ein[u].begin();it!=Ein[u].end();it++){
for(set<int>::iterator j=it->second.begin();j!=it->second.end();j++){
int _u=*j;
int to=it->first;
Eout[to][u].erase(_u);
Ot[to]--;
if(Eout[to][u].size()==0){Eout[to].erase(u);}
ans-=sz[u];
stk.push_back(pi(_u,u));
}
}
ans-=1ll*sz[u]*(sz[u]-1);
ans-=1ll*sz[v]*(sz[v]-1);
ans+=1ll*sz[u]*In[v];
sz[v]+=sz[u];
ans+=1ll*sz[v]*(sz[v]-1);
}
inline void adde(int u,int v){
int fu=find(u), fv=find(v);
if(fu==find(v))return ;
if(Eout[fu].find(fv)!=Eout[fu].end()&&Eout[fu][fv].find(u)!=Eout[fu][fv].end())
{return ;}
Eout[fu][fv].insert(u);
Ein[fv][fu].insert(u);
Ot[fu]++, In[fv]++;
ans+=sz[fv];
if(Eout[fv].find(fu)!=Eout[fv].end()&&Eout[fv][fu].size())
{merge(fu,fv);}
}
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);
stk.push_back(pi(u,v));
while(stk.size()){
pi d = stk.back();
stk.pop_back();
adde(d.first,d.second);
}
printf("%lld\n",ans);
}
}
T3
判断的做法是从后向前,每次找一个最大的没有被占领的,小于等于 \(w_i\) 的位置(如果没有则最后成为了0)
把两个数字相等看成两个不同的数,最后除 \(2^n\) 即可。
\(g[i][j]\) 表示 \(i\) 个数,\(1\) 到 \(j\) 已经被占领了,满足前 \(i\) 个没有最后成为了 \(0\) 的方案数。
转移用 \(g[k-1][k-1]\) 就行了。(具体可以看代码)
\(f[i][j]\) 即以上的东西加上每个位置是否成为了 \(0\) 的限制。转移是类似的。
#include<bits/stdc++.h>
using namespace std;
const int N = 610;
typedef long long ll;
const int mod = 1e9+7;
inline int add(int a,int b){a+=b;return a>=mod?a-mod:a;}
inline int sub(int a,int b){a-=b;return a<0?a+mod:a;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int qpow(int a,int b){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
/* math */
int g[N][N],h[N],n;
int f[N<<1][N],a[N];
int bin[N<<1][N<<1];
int main()
{
cin >> n;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
reverse(a+1,a+n+1);
bin[0][0]=1;for(int i=1;i<=n;i++){
bin[i][0]=1;for(int j=1;j<=i;j++)bin[i][j]=add(bin[i-1][j],bin[i-1][j-1]);
}
g[0][0]=1;
h[0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=i;j++){
g[i][j]=add(g[i][j], g[i-1][j]);
for(int k=1;k+j<=i;k++){
g[i][j+k] = add(g[i][j+k], mul(g[i-1][j], mul(bin[i-1-j][k-1], mul(h[k-1],1+k))));
}
}
h[i] = g[i][i];
}
f[0][0]=1;
a[0]=n*2+1;
for(int i=1;i<=n+1;i++){
int d = a[i-1]-a[i]-1;
for(int j=0;j<i;j++){
int h=j-(n*2-a[i-1]-(i-2));
for(int q=0;q<d;q++){
f[i-1][j]=mul(f[i-1][j], h-q);
}
}
if(i<=n)
for(int j=0;j<=i;j++){
f[i][j]=add(f[i][j], f[i-1][j]);
for(int k=1;k+j<=i;k++){
f[i][j+k] = add(f[i][j+k], mul(f[i-1][j], mul(bin[i-1-j][k-1], mul(h[k-1],1+k))));
}
}
}
int inv2 = (mod+1)>>1;
int ans = f[n][n];
for(int i=1;i<=n;i++)ans=mul(ans,inv2);
cout << ans << endl;
}