World Fragments I 签到结论题
Auspiciousness 组合数学 计数DP
Ama no Jaku 扩展与并查集(01并查集)结论
Koraidon, Miraidon and DFS Shortest Path BFS树上建立支配树,拓扑排序,LCA
Until the Blue Moon Rises 哥德巴赫猜想,分类讨论
Fine Logic 拓扑序,构造
Beautiful Matrix 字符串哈希,Manacher回文串
本题有“三必拿走” ,第一个必拿走,第二个必定拿走,第i个猜对与否,第i+1个必被拿走。有“一不拿走" 第i个猜错,第i+2个一定不再去拿dp[i][j][0/1]代表当前选了i个数字 [第i个数字正在猜] ,j个第一类数字,当前是第几类的方案数。无论第i+1个数字是否被猜对,我们都会选它。故直接由i状态推出选i+1时对答案的贡献。dp存的仅仅是方案数。初始时,dp[0][0][0]=dp[0][0][1]=1,答案直接加上拿了一张牌的方案数,ans+=fac[2n],值得注意的时,这里的“拿了一张牌”指的是全部至少拿了一张牌的方案数,也把拿了三张,两张的“第一张”都包含在内了,故采用方案数可以巧妙的把方案数的累加变成总个数的累加。而后dp[1][1][0]=n,dp[1][0][1]=n,即第一个位置填<=n的有n种方法,>n的也有n种。在此基础上,更新拿了两张牌的方案数 ,(dp[1][1][0]+dp[1][0][1])*fac[2n-1] 即顺利拿了第一张牌后,剩下的2n-1张牌乱拍即可dp[i>=2]的更新方式为每次确立当前能选的第一类数字的[L,R]区间,L=max(0,i-n) R =min(i,n) 然后分两种情况讨论。即当前i应该选择第一类数还是第二类数,i属于的那一段的长度。一旦我们确定了i选第几类数,以第一类数为例,又已知i之前(包括i)第一类数字的个数,那么我们只需要直接组合数学选数即可。按照规则来看,一旦数种类选定,则顺序既定,而顺序既定,则顺序正确,而顺序正确,则证明能够“安全到达i",也就是能进一步选择i+1.综上,本题更像是一道分贡献的组合数学题
# include
using namespace std;
typedef long long int ll;
ll C[610][610],fac[610];
ll dp[610][310][2];
void init(int n,int mod)
{
C[0][0]=1;
C[1][0]=C[1][1]=1;
for(int i=2;i<=n;i++)
{
C[i][0]=C[i][i]=1;
for(int j=1;j>t;
while(t--)
{
int n,m;
cin>>n>>m;
init(2*n,m);
for(int i=0;i<=2*n;i++)
{
for(int j=0;j<=n;j++)
{
dp[i][j][0]=dp[i][j][1]=0;
}
}
dp[0][0][0]=dp[0][0][1]=1;
ll ans=0;
for(int i=1;i<=n+n;i++)
{
int l=max(0,i-n);
int r=min(i,n);
for(int j=l;j<=r;j++)
{
for(int k=1;k<=j;k++)
{
dp[i][j][0]=(dp[i][j][0]+dp[i-k][j-k][1]*C[n-(j-k)][k]%m)%m;
}
for(int k=1;k<=i-j;k++)
{
dp[i][j][1]=(dp[i][j][1]+dp[i-k][j][0]*C[n-(i-j-k)][k]%m)%m;
}
if(i!=n+n)
{
ans=(ans+(dp[i][j][0]+dp[i][j][1])%m*fac[n+n-i]%m)%m;
}
}
}
cout<<(ans+fac[n+n])%m<<'\n';
}
return 0;
}
首先根据样例猜测结论为,只有当全为1或者全为0的时候才能满足要求。那么现在问题变成,给定目标矩阵,每次翻转行或者列,求最小翻转次数。可以利用扩展与并查集,这里为01并查集,代表两种不同的奇偶。当目标矩阵为全1时,若某行为1,则证明其行和列的翻转奇偶性不同,反之相同。做完并查集之后,最多有两个集合。两个集合时,奇偶性不同。也就是一个不反转,一个翻转。让较小者翻转即可。一个集合时,根本不需要翻转。
# include
using namespace std;
typedef long long int ll;
char s[2020][2020];
int f[100000+10],n;
int getf(int x)
{
if(x==f[x])
return f[x];
else
{
f[x]=getf(f[x]);
return f[x];
}
}
void hebing(int x,int y)
{
int t1=getf(x),t2=getf(y);
if(t1!=t2)
f[t1]=t2;
}
int check(char now)
{
for(int i=1; i<=4*n; i++)
{
f[i]=i;
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(s[i][j]!=now)
{
hebing(i,j+3*n);
hebing(j+n,i+2*n);
}
else
{
hebing(i,j+n);
hebing(i+2*n,j+3*n);
}
}
}
for(int i=1; i<=n; i++)
{
if(getf(i)==getf(i+2*n))
return -1;
if(getf(i+n)==getf(i+3*n))
return -1;
}
mapcnt1;
for(int i=1; i<=n; i++)
{
cnt1[getf(i)]++;
cnt1[getf(i+n)]++;
}
int minn1=2*n;
for(auto it:cnt1)
minn1=min(minn1,it.second);
return min(2*n-minn1,minn1);
}
int main ()
{
cin>>n;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
cin>>s[i][j];
}
}
int ans1=check('0');
int ans2=check('1');
if(ans1==-1)
{
cout<<-1;
return 0;
}
cout<
边权为1,BFS得出的正确dis为最短路。DFS乱序遍历的结果,要想保证正确。需要避免以下情况。首先给定图的最短路已经确定。
若有u->v一条边(u!=v)
dis[u]=dis[v]时,一定非法,即先到u后会搅乱v的最短路。
dis[u]
v的边,则这条边就是最短路边,不会对答案造成影响 dis[u]>dis[v]时,若到u之前,v一定会被遍历到,则v已经被标记,最短路不会受到影响。此时v在u最短路的路径上,这点很重要,根据这一点可以得出结论,u v只有在BFS生成树上才可能产生合法的u->v连接。
所以,题目变成了一个求支配树的题目。可以直接套用任意图支配树,也可以根据BFS树的性质---有向无环图(DAG),直接跑一个较为容易实现的有向无环图上的支配树即可。
先进行BFS,若边为最短路边,直接加入新图,若最短路不唯一,也要加入新图(防止将非支配点误判为支配点)。
而DAG上的支配树,从1号点开始拓扑排序,每当遇到度数为0的点,求这些点的LCA,LCA指在支配树上的LCA,然后将这一LCA改为这一点的直系父亲,直系父亲指的也是支配树上的直系父亲。这样拓扑排序后,支配树上一点的全部祖先,都是其支配点。
# include
using namespace std;
typedef long long int ll;
vectorv[500000+10];
vectorpre[500000+10],vv[500000+10];
int book[500000+10],dis[500000+10];
int x[500000+10],y[500000+10];
int fa[500000+10][31],du[500000+10],dep[500000+10];
void swim(int &x,int cha)
{
for(int i=0;i<=30;i++)
{
if((cha)&(1<=0;i--)
{
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
void work(int now)
{
int lca=pre[now][0];
for(auto it:pre[now])
{
lca=getlca(lca,it);
}
dep[now]=dep[lca]+1;
fa[now][0]=lca;
for(int i=1;i<=20;i++)
{
fa[now][i]=fa[fa[now][i-1]][i-1];
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
v[i].clear();
pre[i].clear();
vv[i].clear();
book[i]=0;
dis[i]=1e9;
du[i]=0;
dep[i]=0;
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=20;j++)
{
fa[i][j]=0;
}
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x[i],&y[i]);
if(x[i]==y[i])
continue;
v[x[i]].push_back(y[i]);
}
queueq;
q.push(1);
book[1]=1;
dis[1]=0;
while(!q.empty())
{
int now=q.front();
q.pop();
for(auto it:v[now])
{
if(book[it])
{
if(dis[now]+1==dis[it])
{
pre[it].push_back(now);
vv[now].push_back(it);
du[it]++;
}
}
else
{
dis[it]=dis[now]+1;
pre[it].push_back(now);
vv[now].push_back(it);
book[it]=1;
du[it]++;
q.push(it);
}
}
}
q.push(1);
fa[1][0]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
for(auto it:vv[now])
{
du[it]--;
if(du[it]==0)
{
q.push(it);
work(it);
}
}
}
int flag=0;
for(int i=1;i<=m;i++)
{
if(x[i]==y[i])
continue;
if(dis[x[i]]==dis[y[i]])
{
flag=1;
}
if(dis[x[i]]>dis[y[i]])
{
if(getlca(y[i],x[i])!=y[i])
{
flag=1;
}
}
}
if(flag)
{
cout<<"No"<<'\n';
}
else
{
cout<<"Yes"<<'\n';
}
}
return 0;
}
首先没有环的时候,很显然是拓扑序直接搞就行。一旦有环,考虑到构造一种k=2的方式,即1->n n->1两个排列。这样任意x
#include
using namespace std;
typedef long long ll;
vector v[1000000 + 10];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch))
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch))
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
inline void write(ll x)
{
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
int chu[1000000 + 10], ru[1000000 + 10];
int book[1000000 + 10], tot;
vector ans;
int vis[1000000 + 10];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y;
x = read();
y = read();
v[x].push_back(y);
ru[y]++;
}
int k = 0;
queue q;
for (int i = 1; i <= n; i++)
{
if (ru[i] == 0)
{
q.push(i);
}
}
while (!q.empty())
{
int now = q.front();
q.pop();
tot++;
ans.push_back(now);
for (auto it : v[now])
{
ru[it]--;
if (ru[it] == 0)
{
q.push(it);
}
}
}
if (ans.size() == n)
{
cout << 1 << endl;
for (auto it : ans)
{
cout << it << " ";
}
return 0;
}
else
{
cout << 2 << endl;
for (int i = 1;i<=n;i++)
{
cout << i << " ";
}
cout << endl;
for (int i = n; i >= 1;i--)
{
cout << i << " ";
}
}
return 0;
}