省选联测 42
又垫底了。不懂为什么 T3 都切了。鉴定为人菜。
joke3579 说他演了一整场,那他比较强。
猜数字
思博题。位数是 \(n\lg n+1\)。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
int n;
char s[300010];
int main(){
scanf("%s",s+1);n=strlen(s+1);
if(n==1){
if(s[1]=='1')puts("1");
else puts("2");
return 0;
}
for(int i=3;i<=50000;i++){
if((int)(i*log10(i)+1)==n){
printf("%d\n",i);
break;
}
}
return 0;
}
吵架
原题。ZJOI 捉迷藏。就是线段树找直径。
赛时一开始调了半天点分树。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
int n,q;
struct gra{
int v,next;
}edge[200010];
int t,head[100010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int size[100010],top[100010],dep[100010],son[100010],fa[100010];
void dfs1(int x,int f){
size[x]=1;fa[x]=f;dep[x]=dep[f]+1;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
dfs1(edge[i].v,x);
size[x]+=size[edge[i].v];
if(size[son[x]]<size[edge[i].v])son[x]=edge[i].v;
}
}
}
void dfs2(int x,int f,int tp){
top[x]=tp;
if(son[x])dfs2(son[x],x,tp);
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f&&edge[i].v!=son[x])dfs2(edge[i].v,x,edge[i].v);
}
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
return x;
}
int dis(int x,int y){
if(!x||!y)return -1;
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
int col[100010];
#define lson rt<<1
#define rson rt<<1|1
struct stu{
int d,x,y;
friend stu operator+(const stu&s,const stu&t){
if(s.d==0&&t.d==0)return {dis(s.x,t.x),s.x,t.x};
else if(s.d==-1)return t;
else if(t.d==-1)return s;
stu ans={0,0,0};
if(ans.d<s.d)ans=s;
if(ans.d<t.d)ans=t;
int d=dis(s.x,t.x);
if(ans.d<d)ans={d,s.x,t.x};
d=dis(s.x,t.y);
if(ans.d<d)ans={d,s.x,t.y};
d=dis(s.y,t.x);
if(ans.d<d)ans={d,s.y,t.x};
d=dis(s.y,t.y);
if(ans.d<d)ans={d,s.y,t.y};
return ans;
}
}tree[400010];
void build(int rt,int l,int r){
if(l==r){
col[l]=true;tree[rt]={0,l,0};return;
}
int mid=(l+r)>>1;
build(lson,l,mid);build(rson,mid+1,r);
tree[rt]=tree[lson]+tree[rson];
}
void update(int rt,int L,int R,int pos){
if(L==R){
if(col[pos])tree[rt]={-1,0,0},col[pos]=false;
else tree[rt]={0,pos,0},col[pos]=true;
return;
}
int mid=(L+R)>>1;
if(pos<=mid)update(lson,L,mid,pos);
else update(rson,mid+1,R,pos);
tree[rt]=tree[lson]+tree[rson];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
add(u,v);add(v,u);
}
dfs1(1,0);dfs2(1,0,1);
build(1,1,n);
cin>>q;
while(q--){
char od[5];cin>>od;
if(od[0]=='C'){
int x;cin>>x;update(1,1,n,x);
}
else cout<<tree[1].d<<'\n';
}
return 0;
}
选数游戏 V2
赛时冲了一发 floyd。
首先特判一堆东西。然后问题变成每个数二进制位 \(1\) 或 \(2\) 个 \(1\),找最少的数使得异或和为 \(0\)。然后对于两个 \(1\) 的在它们之间连边,对于一个 \(1\) 的新建个虚点 \(0\) 连边,就变成了最小环。显然不能 floyd,是 \(O(n^3)\)。暴力枚举每个点为起点也是 \(O(n^2)\)。
发现每个环一定经过小于 \(1000\) 的质因数,于是只需要扫这些点作为起点,就可以过了。复杂度 \(O(n\pi(\sqrt n))\)。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
int n,fac[10],ans=0x3f3f3f3f;
struct node{
int v,next;
}edge[200010];
int t,head[1000010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int p[1010];
bool v[1010];
void get(int n=1000){
for(int i=2;i<=n;i++){
if(!v[i])p[++p[0]]=i;
for(int j=1;j<=p[0]&&i*p[j]<=n;j++){
v[i*p[j]]=true;
if(i%p[j]==0)break;
}
}
}
int dis[1000010];
bool vis[1000010];
void bfs(int st){
queue<int>q;
for(int i=1;i<=1000000;i++)dis[i]=0x3f3f3f3f,vis[i]=false;
dis[st]=0;q.push(st);
while(!q.empty()){
int x=q.front();q.pop();vis[x]=true;
for(int i=head[x];i;i=edge[i].next){
if(vis[edge[i].v])continue;
if(dis[edge[i].v]==0x3f3f3f3f){
dis[edge[i].v]=dis[x]+1,q.push(edge[i].v);
}
else ans=min(ans,dis[x]+dis[edge[i].v]+1);
}
}
}
int main(){
scanf("%d",&n);get();
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
if(x==1){
puts("1");return 0;
}
fac[0]=0;fac[2]=1;
for(int j=2;j*j<=x;j++){
int cnt=0;
while(x%j==0)cnt++,x/=j;
if(cnt&1)fac[++fac[0]]=j;
}
if(x>1)fac[++fac[0]]=x;
add(fac[1],fac[2]);add(fac[2],fac[1]);
}
for(int i=1;i<=p[0];i++)bfs(p[i]);
if(ans==0x3f3f3f3f)ans=-1;
printf("%d\n",ans);
}
nnntxdy
无数学。差评。不过 dp 真的好玄妙。我果然永远不会 dp。
首先看着这个 \(n\) 就很状压。那么设 \(dp_{i,S}\) 为克苏恩射 \(i\) 发还剩下 \(S\) 内的活着的概率。考虑转移,两种情况:
- 第 \(i\) 发射死一个:首先能分配的总伤害是射了 \(i-1\) 发减去射死的总血量,需要的伤害是这个随从的血量 \(-1\),是个组合数。不要忘记乘 \(1/\) 活着的随从数量。
- 没射死:需要能分配的伤害能够给每个随从留下最少一滴血。也要乘 \(1/\) 活着的随从数量。
然后考虑如何计算答案。对于活着的集合 \(s\),贡献是显然的,看看怎么算概率。由于我们还没有分配没射死随从的伤害,要考虑一下这些方案。那么分别扫每个活着的随从,乘若干组合数即可。具体的,设个 \(cnt_i\) 为还剩下 \(i\) 点伤害没分配的方案数,要的就是 \(cnt_0\)。枚举每个活着的随从受到多少伤害,每次乘组合数。
反正你要是让我再做一遍我也不会。qnmddp。
(克苏恩为什么不打脸)(算了反正炉石似了)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int mod=998244353;
int n,m,ans,a[20],C[210][210],dp[210][1<<15],inv[210],cnt[210];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
inv[1]=1;
for(int i=2;i<=200;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=0;i<=200;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
dp[0][(1<<n)-1]=1;
for(int i=0;i<m;i++){
for(int s=1;s<(1<<n);s++){
if(!dp[i][s])continue;
int ret=i,sum=0;
for(int j=1;j<=n;j++){
if((s>>j-1)&1)sum+=a[j];
else ret-=a[j];
}
for(int j=1;j<=n;j++){
if(((s>>j-1)&1)&&ret>=a[j]-1)dp[i+1][s^(1<<j-1)]=(dp[i+1][s^(1<<j-1)]+1ll*dp[i][s]*inv[__builtin_popcount(s)]%mod*C[ret][a[j]-1])%mod;
}
if(ret<sum-__builtin_popcount(s))dp[i+1][s]=(dp[i+1][s]+1ll*dp[i][s]*inv[__builtin_popcount(s)])%mod;
}
}
for(int s=0;s<(1<<n);s++){
if(!dp[m][s])continue;
int ret=m,sum=0;
for(int i=1;i<=n;i++){
if((s>>i-1)&1)sum+=a[i];
else ret-=a[i];
}
cnt[ret]=1;
for(int i=0;i<ret;i++)cnt[i]=0;
for(int i=1;i<=n;i++){
if(!((s>>i-1)&1))continue;
for(int j=1;j<=ret;j++){
for(int k=1;k<=min(a[i]-1,j);k++)cnt[j-k]=(cnt[j-k]+1ll*cnt[j]*C[j][k])%mod;
}
}
ans=(ans+1ll*(n-__builtin_popcount(s))*dp[m][s]%mod*cnt[0])%mod;
}
printf("%d\n",ans);
return 0;
}
快踩