AGC002[BCDEF]题解
F是计数于是就做(kan ti jie)了= =
B - Box and Ball
模拟一下 每个盒子开一个d表示有的球数 可能存在红球的打个标记 传递一下就行了
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define inf 20021225
#define ll long long
#define mxn 100010
using namespace std;
bool vis[mxn];
int d[mxn];
int main()
{
int x,y,n,m,ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) d[i]++;
vis[1]=1;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
d[x] --;
d[y] ++;
if(vis[x]) vis[y]=1;
if(!d[x]) vis[x]=0;
}
for(int i=1;i<=n;i++) if(vis[i]) ans++;
printf("%d\n",ans);
return 0;
}
C - Knot Puzzle
很明显只要有一段(或者两段)能>=l就可以 然后细节注意一下就行了
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define inf 20021225
#define ll long long
#define mxn 100010
using namespace std;
int a[mxn],n,l;
int main()
{
int pos=0;
scanf("%d%d",&n,&l);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]+a[i-1]>=l) pos=i;
}
if(!pos) printf("Impossible\n");
else
{
printf("Possible\n");
for(int i=n-1;i>=pos;i--) printf("%d\n",i);
for(int i=1;i<pos;i++) printf("%d\n",i);
}
return 0;
}
D - Stamp Rally
看题以后就有思路了 大概就是从小到大加边然后看一下联通块大小是不是>=k 然而多组询问不能直接做
就需要整体二分= = 去学了一发整体二分和可持久化并查集(然后发现用不到)就写完了 还是比较舒适的
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define inf 20021225
#define ll long long
#define mxn 100010
using namespace std;
struct node{int sz,fa;}t[mxn];// 记得初始化!&&按sz合并
struct cz{int fx,x;}c[mxn];// 存x连到fx
struct query{int x,y,sz;}q[mxn];
struct edge{int x,y;}e[mxn];
int n,m,ans[mxn],id[mxn];
int find(int x)
{
while(t[x].fa!=x) x=t[x].fa;
return x;
}
void merge(int id,int x,int y) // merge y to x
{
x = find(x); y = find(y);
if(x==y) return;
if(t[x].sz < t[y].sz) swap(x,y);
c[id].fx = x; c[id].x = y;
t[y].fa = x; t[x].sz += t[y].sz;
}
void cancel(int id)
{
int x = c[id].fx;int y = c[id].x;
if(!x || !y) return;
t[x].sz -= t[y].sz; t[y].fa = y;
}
int now,c1[mxn],c2[mxn];
void work(int l,int r,int L,int R)// [l,r] query [L,R] val
{
//printf("%d %d %d %d\n**",l,r,L,R);
//for(int i=l;i<=r;i++) printf("%d ",id[i]);
//printf("\n");
if(r<l) return;
if(L==R)
{
for(int i=l;i<=r;i++)
ans[id[i]] = L;
return;
}
int MID = L+R>>1;
while(now<MID) now++,merge(now,e[now].x,e[now].y);
while(now>MID) cancel(now),now--;
int l1,l2; l1=l2=0;
for(int i=l;i<=r;i++)
{
int x=q[id[i]].x,y=q[id[i]].y,k=q[id[i]].sz,fx=find(x),fy=find(y);
int ff=0;
if(fx==fy&&t[fx].sz>=k) ff=1;
if(fx!=fy&&t[fx].sz+t[fy].sz>=k) ff=1;
if(ff) c1[++l1] = id[i];
else c2[++l2] = id[i];
}
int mid = l+l1-1;
for(int i=1;i<=l1;i++) id[l+i-1] = c1[i];
for(int i=1;i<=l2;i++) id[mid+i] = c2[i];
work(l,mid,L,MID); work(mid+1,r,MID+1,R);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) t[i].sz=1,t[i].fa=i;
for(int i=1;i<=m;i++)
scanf("%d%d",&e[i].x,&e[i].y);
int Q;
scanf("%d",&Q);
for(int i=1;i<=Q;i++)
{
scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].sz);
id[i] = i;
}
work(1,Q,1,m);
for(int i=1;i<=Q;i++)
printf("%d\n",ans[i]);
return 0;
}
E - Candy Piles
神仙博弈做不来= =
观察操作其实就是 从大到小选一些拿走 然后其余的是每堆拿一个 (然后我就蠢蠢的开始判定性问题了)
看一发题解还是很神仙的
就是把这些糖摆成阶梯型 然后第一个操作拿走一列 第二个操作拿走一行【似曾相识?有一道考试题也是这个哦 那个是需要dp计数】
继续转换将其变成Lattice Path
然后操作变成向右走或者向上走 然后谁到边界谁就输
然后可以观察到一个斜对角线上的胜负状态是相同的
证明是这个样子 设现在第一个操作使用了x次 第二个操作使用了y次
当(x+1,y+1)是合法操作的时候
1.(x+1,y+1)先手必败也就说明无论(x+1,y)和(x,y+1) 后手就都能把这个状态转移给先手 那么这个时候均为必败态
2.(x+1,y+1)先手必胜 也就说明(x+2,y+1)或者(x+1,y+2)中有先手必败态 然后根据1后手无论是从x到(x+1,y)或者(x,y+1)都是必败的 那么这个时候是先手必胜
所以得到这个性质了 我们的0,0出发点位于的对角线就是x=y那么找到最大的合法(x,x)
这个时候它只能往右走到头 或者往上走到头 判断一下这两个只要有一个是先手必胜(奇数步)那么一定就是先手必胜态
代码实现有点烦= = 0,1数清楚= =
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define inf 20021225
#define ll long long
#define mxn 100010
using namespace std;
int n,a[mxn];
bool cmp(int x,int y)
{
return x>y;
}
void work(int id)
{
int i,qaq=0;
for(i=id+1;a[i]==id&&i<n;i++) qaq^=1;
if((a[id]-id)%2==1||qaq) printf("First\n");
else printf("Second\n");
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]),a[i]--;
sort(a,a+n,cmp); int i;
for(i=0;i<n;i++)
if(a[i+1]<i+1)
{
work(i);break;
}
return 0;
}
F - Leftmost Ball
神仙计数 感觉自己计数水平真的好低= =
观察性质可以得到 每一个颜色的k-1个带颜色球一定是位于其中一个0之后的 那么对于所有的0后面一定是>=k-1的多个是还要加上后面的0的个数的
然后我们发现每放一个0色球 后面可以放的颜色就多一种
然后每放一个非0球 其实可以直接算出后面把这个颜色放完的方案数
就可以列出状态f[x][y]表示放了x个0色球 放完了y种非零色球
观察到 颜色重复计算比较麻烦 于是可以把n!提出来 变成[1,n]这n个颜色顺着放就简洁很多
那么0色球的转移就是 f[x][y]+=f[x-1][y]
非0色球的转移就是 f[x][y] += f[x][y-1] * C(tot-(k-1)*(y-1)-1,k-2)
就是说把这个颜色所有的都放完了很明显是不影响状态的 直接把位置删掉就可以了
最后不要忘了n!
这个题主要的思路就是 通过提取n!来解决多颜色转移之间的互相影响 利用0色可以带来非0色的贡献 来进行转移 做题过程中注意可以通过一个条件来进行转移的可以先不直接计算贡献 保留他的状态 在合适的地方选择转移
计数好好练= =
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define inf 20021225
#define ll long long
#define mdn 1000000007
using namespace std;
int f[2100][2100];
int inv[2100*2100],fac[2100*2100];
int n,k;
int ksm(int bs,int mi)
{
int ans=1;
while(mi)
{
if(mi&1) ans=(ll)ans*bs%mdn;
bs=(ll)bs*bs%mdn; mi>>=1;
}
return ans;
}
int C(int x,int y)
{
if(x<y) return 0;
return (ll)fac[x] * inv[y] %mdn * inv[x-y] %mdn;
}
int main()
{
scanf("%d%d",&n,&k);
if(k==1||n==1){printf("1\n");return 0;}
inv[0]=fac[0]=1; int tot=n*k;
for(int i=1;i<=tot;i++) fac[i]=(ll)fac[i-1]*i%mdn;
inv[tot] = ksm(fac[tot],mdn-2);
for(int i=tot-1;i;i--) inv[i]=(ll)inv[i+1]*(i+1)%mdn;
f[0][0]=1;// printf("%d\n",inv[tot]);
for(int i=1;i<=n;i++)
{
for(int j=0;j<=i;j++)
{
f[i][j] += f[i-1][j];
if(j) f[i][j] += (ll)f[i][j-1] * C(tot-i-(k-1)*(j-1)-1,k-2) %mdn;
f[i][j]%=mdn;
//printf("%d %d %d\n",i,j,f[i][j]);
}
}
printf("%d\n",(ll)f[n][n]*fac[n]%mdn);
return 0;
}