B. Coffee Chicken
大致题意:
- S(1)="COFFEE";
- S(2)="CHICKEN";
- S(n)=S(n-2):S(n-1)----即第n-2个字符串作为前缀,第n-1个字符串作为后缀.
T组询问,求第n个字符串中第k个位置的字符.(1<=T<=1000,1<=n<=500,k<=min{ |S(n)|,1e12} )
分析:
- 我们可以处理出长度<=1e12的字符串一共57个
- 我们可以直接对长度进行dfs分治求pos位置.需要注意的是n>56时,前缀字符串跟n的奇偶性有关.
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll a[502]; string s[3]; void dfs( ll x,ll k,ll r ) { if( x<=2 ) { for( int i=k-1;i<min(r,(ll)s[x].size());i++ ) cout<<s[x][i]; return ; } if( r<=(ll)a[x-2] ) dfs(x-2,k,r); else if( k<=a[x-2] && r>a[x-2] ) { dfs(x-2,k,a[x-2]); dfs(x-1,1,r-a[x-2]); } else { k-=a[x-2]; dfs(x-1,k,r-a[x-2]); } } int main() { s[1]="COFFEE";s[2]="CHICKEN"; a[1]=6,a[2]=7; for( int i=3;i<=57;i++ ) a[i]=a[i-1]+a[i-2]; int t; scanf("%d",&t); while( t-- ) { ll n,k; scanf("%lld%lld",&n,&k); if( n>56 ) { if( n%2==0 ) n=56; else n=57; } dfs(n,k,k+9); puts(""); } }
C. Gifted Composer
大致题意:七个音符{do re mi fa sol la si},有一个空序列,n次操作,每次操作在序列头部或尾部插入一个音符,问每次操作有多少种循环节.----循环节定义 能划分成k个重复的部分 后面允许跟着一个不完整的部分(完整部分的初始部分)
分析:对字符串进行哈希处理,记录每次操作对应字符串S的区间[L,R],枚举循环节长度为i,二分找到符合字符串的最后出现的操作pos,那么长度i的循环节的贡献[i,pos],利用差分统计答案即可.
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=2e6+10; const int inf=0x3f3f3f3f; const int mod=1e9+7; using namespace std; char s[10],t[maxn],ss[10]; int L[maxn],R[maxn]; int a[maxn]; struct Hash{ ll p[maxn],hash[maxn],base=131; ll getHash(int l,int r ) { ll ans=(hash[r]-hash[l-1]*p[r-l+1])%mod; return (ans+mod)%mod; } void init( char *buf ) { int n=strlen(buf); p[0]=1; for( int i=1;i<=n;i++ ) p[i]=p[i-1]*base%mod; for( int i=1;i<=n;i++ ) { hash[i]=( hash[i-1]*base%mod+(buf[i-1]-'a'+1) )%mod; } } }hh; int check( int q,int x ) { return ( hh.getHash(L[q],R[q]-x)==hh.getHash(L[q]+x,R[q]) ); } int main() { int n,head=1e6+1,tail=1e6+2; scanf("%d",&n); for( int i=1;i<=n;i++ ) { scanf("%s%s",ss,s); char c=s[0]; if( c=='s' && s[1]=='i' ) c='z'; if( ss[0]=='a' ) t[tail++]=c; else t[head--]=c; L[i]=head+1,R[i]=tail-1; } for( int i=1;i<=n;i++ ) L[i]-=head,R[i]-=head; t[tail]=0; hh.init(t+head+1); for( int i=1;i<=n;i++ ) { int l=i,r=n; while( l<=r ) { int mid=(l+r)>>1; if( check(mid,i) ) l=mid+1; else r=mid-1; } a[i]++,a[r+1]--; } for( int i=1;i<=n;i++ ) { a[i]+=a[i-1]; printf("%d\n",a[i]); } return 0; }
D. Han Xin and His Troops
大致题意:给定n个( x=b( mod a ) )同余方程,求最小的x满足所有方程.(n<=100,m<=1e18,0<b<a<1e5 )
分析:两种解法.
- 1.套用CRT板子-----注意爆longlong 要用__int128
#include<iostream> #include<cstdio> using namespace std; const int maxn=1e5+10; typedef __int128 ll; ll m[maxn]; ll a[maxn]; ll m1; void read(ll &x ) { x=0;int f=1;char s=getchar(); for( ;!isdigit(s);s=='-' && (f=-1),s=getchar()); for( ;isdigit(s);x=x*10+s-48,s=getchar()); x*=f; } inline void print( ll x ) { if( x<0 ) { x=-x; putchar('-'); } if( x>9 ) print(x/10); putchar( x%10+'0' ); } struct node{ ll exgcd( ll a1,ll b,ll &x,ll &y ) { if( b==0 ) { x=1;y=0; return a1; } exgcd(b,a1%b,x,y); ll r=exgcd( b,a1%b,y,x ); y-=x*(a1/b); return r; } ll solve( ll *a,ll *m,ll n ) { ll c=a[0],l=m[0]; ll d,x,y; for(int i=1;i<n;i++ ) { d=exgcd( l,m[i],x,y ); if( ( a[i]-c)%d ) return -1; x=( a[i]-c )/d*x%( m[i]/d ); c+=l*x; l=l/d*m[i]; c%=l; } return c >= 0 ? c : c+l; } }CRT; int main() { ll n; read(n);read(m1); for( int i=0;i<n;i++ ) { read(m[i]); read(a[i]); } ll ans=CRT.solve(a,m,n); if( ans==-1 ) printf("he was definitely lying\n"); else if( ans>m1 ) printf("he was probably lying\n"); else print(ans); }
- 2.出题人推荐的方法.利用LCM(p1,p2...p-1)=y, 在有解的情况下满足pi同余式,ans+=y,可以证明加的次数不超过pi次.并且该方法不会爆long long. ( 有解判定,n个模数是否互质 ).-------该方法只适用于模数较小情况.
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll a[105],b[105]; ll n,m; void solve() { ll ans=b[1]; ll tmp=a[1]; for( int i=1;i<=n;i++ ) { for( int j=1;j<=i;j++ ) { int gd=__gcd(a[i],a[j]); if( b[i]%gd!=b[j]%gd ) { puts("he was definitely lying"); return; } } } for( int i=2;i<=n;i++ ) { for( int j=0;j<a[i] && ans<=m && ans%a[i]!=b[i] ;j++ ) ans+=tmp; if( ans>m ) { puts("he was probably lying"); return; } tmp=tmp/__gcd(tmp,a[i])*a[i]; } printf("%lld\n",ans); } int main() { scanf("%lld%lld",&n,&m); for( int i=1;i<=n;i++ ) scanf("%lld%lld",&a[i],&b[i]); solve(); }
F. Popping Balloons
大致题意:给定n*n的矩阵,有n个气球分布在矩阵中,我们横着打三枪,竖着打三枪,打枪 横/竖 间隔为r.求最多能打掉多少气球.
分析:该题目有三种方法.
1.出题人解法.-----暂无标程
暴力枚举横向打法.我们考虑打x1,x1+r,x1+2 * r 三枪,用g(i)表示i列气球个数和.横向打枪的打中气球(x,y),会影响列向气球个数.那么我们可以用一个muliset维护列向气球个数.对于横向每一个方案,对于影响的列向气球个数进行log(n)取出修改插入.我们可以计算每个气球的操作次数为9log(n) .那么该方案的复杂度为O(9n*log(n) )
2.解法二
我们处理出所有横向打法的方案贡献并排序.最终答案肯定出现在最大的几个方案中,那么我们可以暴力计算最大的100个方案,进行更新即可。(实测只需要暴力计算最大的4..5个方案)
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; int n,r,fx[maxn*3],fy[maxn*3],res; struct node{ int x,y; }a[maxn],w[maxn]; bool cmp( node xx,node yy ) { return xx.x>yy.x; } int main() { cin>>n>>r; memset(fx,0,sizeof(fx)); for( int i=0;i<n;i++ ) { cin>>a[i].x>>a[i].y; fx[a[i].x]++; } for( int i=0;i<maxn;i++ ) { w[i].x=fx[i]+fx[i+r]+fx[i+2*r]; w[i].y=i; } sort(w,w+maxn,cmp); for( int i=0;i<=4;i++ ) { memset(fy,0,sizeof(fy)); for( int j=0;j<n;j++ ) { if( a[j].x!=w[i].y && a[j].x!=w[i].y+r && a[j].x!=w[i].y+2*r ) { fy[a[j].y]++; } } int my=0; for( int j=0;j<maxn;j++ ) my=max( my,fy[j]+fy[j+r]+fy[j+2*r] ); res=max(res,my+w[i].x); } cout<<res<<endl; }
3.解法三----rank1 杜大佬的方法 复杂度O(n) 暂时没搞懂.......鸽了.....
G.Road Construction
大致题意:给定n个点的坐标,请画一条直线满足(n<=300,xi,yi<=1e9)
- 将点均分成两部分。
- 并且直线上不过点
- 使得所有点到直线的距离的最小值 最大.
分析:我们可以得到结论----满足直线一定是平行某两个点的连线或者是某两点连线的中垂线。(简单证明即可).
n不超过300,然后我们枚举所有的斜率,把所有点按斜率排序,取中间两个点,让这两个点到直线距离相等,这个距离就是当前的最小距离。然后求最小距离最大值。复杂度O(n^3*logn)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=302; struct node{ double x,y,b; bool operator < ( const node &t )const{ return b<t.b; } }p[maxn]; double x[maxn],y[maxn]; double dis( double k,double b,double x,double y ) { return fabs(k*x+b-y)/sqrt(k*k+1.0); } int main() { int n; scanf("%d",&n); for( int i=1;i<=n;i++ ) { scanf("%lf%lf",&x[i],&y[i]); p[i].x=x[i]; p[i].y=y[i]; } double ans=0; for( int i=1;i<=n;i++ ) { for( int j=i+1;j<=n;j++ ) { double k=(y[i]-y[j])/(x[i]-x[j]); // 平行的情况 for( int l=1;l<=n;l++ ) { p[l].b=p[l].y-k*p[l].x; } sort(p+1,p+1+n); double b=(p[n/2].b+p[n/2+1].b)/2; ans=max( ans,dis(k,b,p[n/2].x,p[n/2].y) ); k=-1.0/k; // 垂直的情况 for( int l=1;l<=n;l++ ) { p[l].b=p[l].y-k*p[l].x; } sort(p+1,p+1+n); b=(p[n/2].b+p[n/2+1].b)/2; ans=max( ans,dis(k,b,p[n/2].x,p[n/2].y) ); } } printf("%.10lf\n",ans); return 0; }
---nth_element可以将复杂度降至O(n^3)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=302; double x[maxn],y[maxn],b[maxn],ans; int n; void solve( double k ) { for( int i=0;i<n;i++ ) b[i]=y[i]-k*x[i]; nth_element(b,b+n/2,b+n); double d1=b[n/2]; double d2=*max_element(b,b+n/2); double w=(d1-d2)/sqrt(1+k*k); ans=max(ans,w/2); } int main() { scanf("%d",&n); for( int i=0;i<n;i++ ) scanf("%lf%lf",&x[i],&y[i]); for( int i=0;i<n;i++ ) { for( int j=i+1;j<n;j++ ) { double k=(y[i]-y[j])/(x[i]-x[j]); solve(k); solve(-1.0/k); } } printf("%.12lf\n",ans); return 0; }
H.Stammering Chemists
大致题意:给定6个点,5条边.判断该图形结构.
分析:按点的度判断即可.
#include<bits/stdc++.h> using namespace std; // 1 2 3 4 // 2 4 n-hexane // 3 2 1 2-methylpentane // 3 2 1 3-methylpentane // 4 0 2 2,3-dimethylbutane // 4 2 0 1 2,2-dimethylbutane vector<int>g[10]; int ans=0; void dfs( int x,int f ,int dep ) { ans=max(ans,dep); for( auto v:g[x] ) { if( v==f ) continue; dfs(v,x,dep+1); } } int main() { int t; cin>>t; while( t-- ) { ans=0; int vis[5]={0}; for( int i=1;i<=6;i++ ) g[i].clear(); for( int i=1;i<6;i++ ) { int u,v;cin>>u>>v; g[u].push_back(v); g[v].push_back(u); } for( int i=1;i<=6;i++ ) vis[ g[i].size() ]++; if( vis[1]+vis[2]==6 ) puts("n-hexane"); else if( vis[1]+vis[3]==6 ) puts("2,3-dimethylbutane"); else if( vis[1]+vis[2]+vis[4]==6 ) puts("2,2-dimethylbutane"); else { int s; for( int i=1;i<=6;i++ ) if( g[i].size()==3 ) s=i; dfs(s,0,0); if( ans==2 ) puts("3-methylpentane"); else puts("2-methylpentane"); } } }