Forethought Future Cup - Elimination Round
窝也不知道这是个啥比赛QwQ
A. Love "A"
给你一个串,你可以删去若干个元素,使得最后
a
的个数严格大于一半。求最大串长。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char s[55];int v=0,n;
int main()
{
scanf("%s",s+1);n=strlen(s+1);
for(int i=1;i<=n;++i)v+=(s[i]=='a');
while(v*2<=n)--n;
printf("%d
",n);
return 0;
}
B. Hate "A"
给你一个串(s),定义(s')为(s)删去所有
a
后的串,定义(t=s+s')。
现在给你(t),你需要还原一组合法的(s)。
找到极大位置满足前面删去a
之后的串长和后面相等,判断一下两个串是否相等就行了。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 100100
char s[MAX],a[MAX];
int n,t;
void check(int p)
{
for(int i=p;i<=n;++i)
if(s[i]!=a[i-p+1])return;
for(int i=1;i<p;++i)putchar(s[i]);
puts("");exit(0);
}
int main()
{
scanf("%s",s+1);n=strlen(s+1);
for(int i=1;i<=n;++i)
{
if(s[i]!='a')a[++t]=s[i];
if(n-i==t)check(i+1);
}
puts(":(");
return 0;
}
C. Tree Diameter
交互题。
有一棵树,每次你可以询问两个集合,会告诉你从这两个集合中各取一个点的最大距离。现在你要求出树的直径。
询问次数不超过(9)次。
(nle 100),边权不超过(100)。
我们只需要确定直径的一个端点,然后询问这个点到其他所有点的距离的最大值就行了。
一个很呆的想法是我们每次随机把点集分成两半,然后求一个最大值,但是这样子的正确率是(frac{1}{512}),显然是不够的。
我们知道这样一个结论:距离树上某个点的最远点一定是直径的一个端点。
那么我们先对于(1)求出到其他所有点的最远距离,这样子对于剩余的点集进行二分就可以求出一个直径的端点,再询问一次就是答案啦。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int Query(vector<int> &A,vector<int> &B)
{
printf("%d %d ",A.size(),B.size());
for(int v:A)printf("%d ",v);
for(int v:B)printf("%d ",v);
puts("");fflush(stdout);
int x;scanf("%d",&x);
return x;
}
int main()
{
int T,n;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
vector<int> A,B;
A.push_back(1);
for(int i=2;i<=n;++i)B.push_back(i);
int now=Query(A,B);
int l=0,r=n-2;
while(l<r)
{
int mid=(l+r)>>1;
vector<int> a,b;
for(int i=0;i<=mid;++i)a.push_back(B[i]);
for(int i=mid+1;i<n-1;++i)b.push_back(B[i]);
if(Query(A,a)==now)r=mid;
else l=mid+1;
}
int p=B[l];
A.clear();B.clear();
A.push_back(p);
for(int i=1;i<=n;++i)if(i!=p)B.push_back(i);
printf("-1 %d
",Query(A,B));fflush(stdout);
}
return 0;
}
D. Frog Jumping
有一只青蛙,初始时在(0)位置,每次可以沿数轴正方向跳(a)个单位或者是沿着负方向跳(b)个单位。
定义(f(x))为在不跳出区间([0,x])的情况下,能够跳到的位置个数。
求(sum_{i=0}^m f(i))。
(a,ble 10^5,mle 10^9)。
先来看看(f(n))怎么求,大力猜想一下一定能够从(f(n-a))转移过来,然后再考虑一下中间这(a)步能够走到些什么地方,大力猜猜可以走到所有((n-a)+gcd(a,b))倍数的位置,所以大力猜想一下(f(n+a)=f(n)+frac{a}{gcd(a,b)})。
然而这样子在(n)很小的时候肯定是假的,估摸一下(nge a+b)的时候是真的,其他时候是假的。
但是(a+ble 2*10^5),所以我们可以考虑大力暴力计算一下答案。
怎么算呢?设(f[i])表示经过不超过最小的(f[i])可以到达(i)位置。这个东西显然就是一个最短路的问题,跑一遍(dijkstra)就可以算出来。
剩下的就是一个简单的计算问题啦。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define pi pair<ll,int>
#define mp make_pair
#define MAX 1000000
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
ll dis[MAX],f[MAX];
bool vis[MAX];
int m,a,b,N;
priority_queue<pi,vector<pi>,greater<pi> >Q;
int main()
{
m=read();a=read();b=read();N=a+a+b;
memset(dis,63,sizeof(dis));
dis[0]=0;Q.push(mp(0,0));
while(!Q.empty())
{
int u=Q.top().second;Q.pop();
if(vis[u])continue;vis[u]=true;
if(u>=b&&dis[u-b]>dis[u])dis[u-b]=dis[u],Q.push(mp(dis[u-b],u-b));
if(u+a<=N&&dis[u+a]>max(0ll+u+a,dis[u]))dis[u+a]=max(0ll+u+a,dis[u]),Q.push(mp(dis[u+a],u+a));
}
ll ans=0;int mm=min(m,a+b-1);
for(int i=0;i<=N;++i)
if(dis[i]<=N)f[dis[i]]+=1;
for(int i=1;i<=N;++i)f[i]+=f[i-1];
for(int i=0;i<=mm;++i)ans+=f[i];
if(m>=a+b)
{
int d=a/__gcd(a,b);
for(int i=a+b,j=0;j<a&&i<=m;++j,++i)
{
int L=(m-i)/a;
ans+=(L+1)*f[i]+1ll*L*(L+1)/2*d;
}
}
printf("%lld
",ans);
return 0;
}
E. Hot is Cold
一开始有一个数列,你要执行(n)次操作,每次给你一个(>x)或者(<x),如果一个数带进去合法,就把他变成相反数,求出最后的数列。
(n,Qle 2*10^5,-10^5le a_ile 10^5)
显然只需要对于每个值算答案最后再询问就好了。
那么拿一个线段树维护值域,记录每个值是否被翻转过。
那么每次大力讨论,发现只需要维护区间赋值和区间翻转就行了。
注意这里是(<)和(>),所以要特殊处理一下(x)和(-x)这两个位置。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 200200
#define lson (now<<1)
#define rson (now<<1|1)
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int t[MAX<<2],tag[MAX<<2],rev[MAX<<2];
void puttag(int now,int v){t[now]=tag[now]=v;rev[now]=0;}
void putrev(int now){t[now]^=1;rev[now]^=1;}
void pushdown(int now)
{
if(~tag[now])
{
puttag(lson,tag[now]);
puttag(rson,tag[now]);
tag[now]=-1;
}
if(rev[now])
{
putrev(lson);putrev(rson);
rev[now]=0;
}
}
void Modify(int now,int l,int r,int L,int R,int w)
{
if(L>R)return;
if(L<=l&&r<=R){puttag(now,w);return;}
int mid=(l+r)>>1;pushdown(now);
if(L<=mid)Modify(lson,l,mid,L,R,w);
if(R>mid)Modify(rson,mid+1,r,L,R,w);
}
void Modify(int now,int l,int r,int L,int R)
{
if(L>R)return;
if(L<=l&&r<=R){putrev(now);return;}
int mid=(l+r)>>1;pushdown(now);
if(L<=mid)Modify(lson,l,mid,L,R);
if(R>mid)Modify(rson,mid+1,r,L,R);
}
int Query(int now,int l,int r,int p)
{
if(l==r)return t[now];
int mid=(l+r)>>1;pushdown(now);
if(p<=mid)return Query(lson,l,mid,p);
else return Query(rson,mid+1,r,p);
}
int n,Q,x,a[MAX];char ch[MAX];
const int N=1e5;
int main()
{
n=read();Q=read();
for(int i=1;i<=n;++i)a[i]=read();
memset(tag,-1,sizeof(tag));
while(Q--)
{
scanf("%s",ch);x=read();
if(ch[0]=='>')
{
if(x>=0)Modify(1,-N,N,x+1,N,1),Modify(1,-N,N,-N,-x-1,0);
else
{
Modify(1,-N,N,x+1,-x-1),Modify(1,-N,N,-x+1,N,1),Modify(1,-N,N,-N,x-1,0);
if(!Query(1,-N,N,-x))Modify(1,-N,N,-x,-x);
if(Query(1,-N,N,x))Modify(1,-N,N,x,x);
}
}
else
{
if(x<=0)Modify(1,-N,N,-N,x-1,1),Modify(1,-N,N,-x+1,N,0);
else
{
Modify(1,-N,N,-x+1,x-1),Modify(1,-N,N,-N,-x-1,1),Modify(1,-N,N,x+1,N,0);
if(!Query(1,-N,N,-x))Modify(1,-N,N,-x,-x);
if(Query(1,-N,N,x))Modify(1,-N,N,x,x);
}
}
}
for(int i=1;i<=n;++i)printf("%d ",Query(1,-N,N,a[i])?-a[i]:a[i]);
puts("");return 0;
}
F. Leaf Partition
给你一棵树,你要把叶子节点分成若干个集合,使得每个集合的(f(S))都无交集。(f(S))为最小的连通图使得所有(S)中的叶子节点都被连通。
求划分的方案数。
(nle 2*10^5)
设(f[i][0/1/2])表示当前考虑以(i)为根的子树,(0)表示当前点不会被划分到任何一个集合中,(1)表示和(1)个儿子相连,(2)表示和两个以上的儿子相连,转移就很简单了。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAX 200200
#define MOD 998244353
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int n,f[MAX][3];
vector<int> E[MAX];
void dfs(int u)
{
if(E[u].empty())f[u][2]=1;else f[u][0]=1;
for(int v:E[u])
{
dfs(v);
f[u][2]=(1ll*f[u][2]*(f[v][0]+f[v][2])+1ll*(f[u][1]+f[u][2])*(f[v][1]+f[v][2]))%MOD;
f[u][1]=(1ll*f[u][1]*(f[v][0]+f[v][2])+1ll*f[u][0]*(f[v][1]+f[v][2]))%MOD;
f[u][0]=1ll*f[u][0]*(f[v][0]+f[v][2])%MOD;
}
}
int main()
{
n=read();
for(int i=2;i<=n;++i)E[read()].push_back(i);
dfs(1);printf("%d
",(f[1][0]+f[1][2])%MOD);
return 0;
}
G. Zoning Restrictions
一个人要在一条路上修(n)栋高度为([0,h])的房子,假设修了一栋高度为(a)的房子就会产生收益(a^2)。有(m)个限制,每个限制([l,r],x,c),即在([l,r])这些房子中,如果最高的房子严格大于了(x),就要交(c)的罚款。
求最大收益。
(n,h,mle 50)
简单网络流?
先把每个点拆成(h+1)个点,然后串起来,连边为选择(a)时减少的代价。
对于一个限制([l,r]),把对应的第(x+1)个点和一个新点连起来,边权为(inf),再把新点和(T)相连,代价为罚款的值。
然后(n*h*h)减去最大流就是答案了。
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
#define MAX 3000
const int inf=1e9;
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Line{int v,next,w;}e[1000000];
int h[MAX],cnt=2;
inline void Add(int u,int v,int w)
{
e[cnt]=(Line){v,h[u],w};h[u]=cnt++;
e[cnt]=(Line){u,h[v],0};h[v]=cnt++;
}
int S,T;
int level[MAX];
bool bfs()
{
for(int i=S;i<=T;++i)level[i]=0;
queue<int> Q;Q.push(S);level[S]=1;
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=h[u];i;i=e[i].next)
if(!level[e[i].v]&&e[i].w)
level[e[i].v]=level[u]+1,Q.push(e[i].v);
}
return level[T];
}
int dfs(int u,int flow)
{
if(u==T||!flow)return flow;
int ret=0;
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v,d;
if(level[v]==level[u]+1)
{
d=dfs(v,min(flow,e[i].w));
ret+=d;flow-=d;
e[i].w-=d;e[i^1].w+=d;
}
}
return ret;
}
int Dinic()
{
int ret=0;
while(bfs())ret+=dfs(S,inf);
return ret;
}
int a[55][55],tot,ans;
int n,H,m;
int main()
{
n=read();H=read();m=read();
S=0;T=n*(H+1)+m+1;ans=n*H*H;
for(int i=1;i<=n;++i)
for(int j=0;j<=H;++j)a[i][j]=++tot;
for(int i=1;i<=n;++i)Add(S,a[i][0],inf);
for(int i=1;i<=n;++i)
for(int j=0;j<H;++j)
Add(a[i][j],a[i][j+1],H*H-j*j);
while(m--)
{
int l=read(),r=read(),x=read(),c=read();
if(x==H)continue;++x;++tot;
for(int i=l;i<=r;++i)Add(a[i][x],tot,inf);
Add(tot,T,c);
}
printf("%d
",ans-Dinic());
return 0;
}
H. Satanic Panic
平面上有一堆点,你要求五角星的数量,保证不存在三点共线。
(nle 300)。
本质上就是求五个点的凸包数量,也就是要找五条极角序递增的边。
把所有边按照极角序排序。
设(f[i][j][1..5])表示从(i)点出发,当前到了(j),且中间确定了(1..5)条边的方案数。
每次新增一条边进来转移。
时间复杂度(O(n^3))。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 330
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Node{int x,y;}p[MAX];
Node operator-(Node a,Node b){return (Node){a.x-b.x,a.y-b.y};}
Node operator+(Node a,Node b){return (Node){a.x+b.x,a.y+b.y};}
ll Cross(Node a,Node b){return 1ll*a.x*b.y-1ll*b.x*a.y;}
bool operator<(Node a,Node b){return Cross(a,b)<0;}
struct Line{Node v;int a,b;};
bool operator<(Line a,Line b)
{
if(a.v<b.v)return true;
if(b.v<a.v)return false;
if(a.a!=b.a)return a.a<b.a;
return a.b<b.b;
}
int n;
vector<Line> E;
ll f[MAX][MAX][6];
int main()
{
n=read();
for(int i=1;i<=n;++i)p[i].x=read(),p[i].y=read();
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(i!=j)E.push_back((Line){(Node){p[j]-p[i]},i,j});
sort(E.begin(),E.end());
for(auto a:E)
{
int u=a.a,v=a.b;
f[u][v][1]+=1;
for(int i=1;i<=5;++i)
for(int j=1;j<=n;++j)
f[j][v][i+1]+=f[j][u][i];
}
ll ans=0;
for(int i=1;i<=n;++i)ans+=f[i][i][5];
printf("%lld
",ans);
return 0;
}