Moscow Pre-Finals Workshop 2020 - Legilimens+Coffee Chicken Contest
Moscow Pre-Finals Workshop 2020 - Legilimens+Coffee Chicken Contest (XX Open Cup, Grand Prix of Nanjing)
这场的题目来源主要是19年多校。
A. Everyone Loves Playing Games(线性基 带悔贪心)
线性基+带悔贪心,具体我先咕了
#include <bits/stdc++.h>
#define pb emplace_back
#define pii pair<int, int>
#define fir first
#define sec second
#define ll long long
using namespace std;
#define gc() getchar()
inline int read()
{
int now=0,f=1; char c=gc();
for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now*f;
}
inline ll readll()
{
ll now=0,f=1; char c=gc();
for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now*f;
}
const int mod = 1e9+7;
inline int add(int a,int b){return a+b>=mod? a+b-mod: a+b;}
inline int sub(int a,int b){return a<b? a-b+mod: a-b;}
inline int mul(int a,int b){return 1LL*a*b%mod;}
int qpow(int a,int b){
int ret=1;
for(; b; b>>=1){
if(b&1) ret=mul(ret,a);
a=mul(a,a);
}
return ret;
}
inline int rev(int x){return qpow(x,mod-2);}
const int N = 1e4+10;
const int M = 60;
struct LB{
ll b[65];
void clear(){memset(b,0,sizeof(b));}
void ins(ll x){
for(int i=M; i>=0; --i){
if((x>>i)&1){
if(!b[i]) { b[i]=x; return;}
x ^= b[i];
}
}
}
} A, B;
void solve(){
int n=read(), m=read();
A.clear(), B.clear();
ll X = 0;
for(int i=1; i<=n; i++){
ll x=readll(), y=readll();
X ^= x; A.ins(x^y);
}
for(int i=1; i<=m; i++){
ll x=readll(), y=readll();
X ^= x; B.ins(x^y);
}
ll ans=X;
for(int i=M; i>=0; --i){
if(!A.b[i] && !B.b[i]) continue;
if(A.b[i] && B.b[i]){
if((ans>>i)&1) ans ^= A.b[i];
A.ins(A.b[i]^B.b[i]);
}
else if(A.b[i]) ans=max(ans, ans^A.b[i]);
else if(B.b[i]) ans=min(ans, ans^B.b[i]);
}
printf("%lld\n",ans);
}
int main(){
int T=read();
while(T--) solve();
return 0;
}
B. Gifted Composer(二分 Hash)
\(Description\)
初始有一个空字符串,\(n\)次操作,每次在串的前面或后面加一个字符,求每次操作后字符串的循环节种数。
\(s\)的循环节\(t\)定义为:\(s\)由\(k\)个\(t\)组成或由\(k\)个\(t\)加\(t\)的一个前缀组成。
\(n\leq 10^6\)。
\(Solution\)
给定\(s\)和长度\(k\),判断\(s\)是否有上述循环节 等价于 判断是否有\(s[1,n-k]==s[k+1,n]\)。哈希即可\(O(1)\)判断。
有个性质是:长度为\(k\)的循环节会在\(k\)时刻出现,若它在\(k+t\)时刻消失,则以后不会再出现。
因为多一个字符导致\(k\)循环节消失后,再在前面或后面加字符,它也不会再成为循环节。
所以先对最终串哈希。枚举循环节长度\(k\),二分它消失的时刻\(k+t\),使\([k,k+t]\)的答案\(+1\)即可。
//655ms 46000KB
#include <bits/stdc++.h>
#define pc putchar
#define gc() getchar()
#define pb emplace_back
#define seed 31
typedef long long LL;
typedef unsigned long long ull;
const int N=1e6+6;
int sum[N],L[N],R[N];
ull hs[N*3],pw[N];
char s[N*3];
inline int read()
{
int now=0,f=1; char c=gc();
for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now*f;
}
inline char Get()
{
char c=gc(); while(!isalpha(c)) c=gc();
return c;
}
inline ull Hash(int l,int r)
{
return hs[r]-hs[l-1]*pw[r-l+1];
}
bool Check(int t,int k)
{
return Hash(L[t],R[t]-k)==Hash(L[t]+k,R[t]);
}
int main()
{
int n=read(),h=1e6+1,t=1e6+1;
static char ss[5];
for(int i=1; i<=n; ++i)
{
if(Get()=='a') scanf("%s",ss), s[t++]=ss[0]=='s'?ss[1]:ss[0];
else scanf("%s",ss), s[--h]=ss[0]=='s'?ss[1]:ss[0];
L[i]=h, R[i]=t-1;
}
pw[0]=1;
for(int i=1; i<=n; ++i) pw[i]=pw[i-1]*seed;
for(int i=h; i<t; ++i) hs[i]=hs[i-1]*seed+s[i]-'a';
for(int i=1; i<=n; ++i)
{
int l=i,r=n,mid;
while(l<r)
if(Check(mid=l+r+1>>1,i)) l=mid;
else r=mid-1;
++sum[i], --sum[l+1];
}
for(int i=1; i<=n; ++i) sum[i]+=sum[i-1], printf("%d\n",sum[i]);
return 0;
}
D. String Theory(后缀数组) √
做过的题,见这儿。
G. Blackjack(概率DP 退背包)
\(Description\)
给定\(a,b,n\)和\(n\)个数。从\(n\)个数中依次取一个数,直到当前取的数的和\(sum\gt a\)。若此时\(sum>b\)则输,否则赢。求\(n\)个数随机排列的情况下赢的概率。
\(n,a,b\leq 500\)。
\(Solution\)
问题在于如何表示任意一种排列,感觉是个套路。
\(f[i][j][k]\)表示前\(i\)个数,选了其中\(j\)个,选的数的和为\(k\)的概率。则当前排列中含前\(i\)个数的\(j\)个数。所以:
注意要乘\(j\)!因为不乘\(j\)还是按顺序取的,不表示所有排列,乘\(j\)即在运算中乘了\(j!\),表示取了\(j\)个数的所有排列的情况。
注意这是个加法的背包,所以可以退背包,即:
枚举第\(i\)个数,令它作为最后选中的数,然后退背包使它的贡献从\(f[n]\)中删去。
那么\(A_i\)作为最后选中的数且合法的情况即为:
注意要除以\(n-j\)!即当前已选了\(j\)个限定\(i\)为最后一个的概率。
系数的细节也太容易漏了。
复杂度\(O(n^3)\)。
PS:CF上除法还挺快的... 不需要存\(\frac1i\)的数组变成乘法。
//358ms 4000KB
#include <bits/stdc++.h>
#define pc putchar
#define gc() getchar()
#define pb emplace_back
typedef long long LL;
const int N=505;
int A[N];
double f[N][N],g[N][N];
inline int read()
{
int now=0,f=1; char c=gc();
for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now*f;
}
int main()
{
int n=read(),a=read(),b=read();
for(int i=1; i<=n; ++i) A[i]=read();
f[0][0]=1;
for(int i=1,sum=0; i<=n; ++i)
{
sum=std::min(sum+A[i],b);
for(int j=i,t=A[i]; j; --j)
for(int k=sum; k>=t; --k)
f[j][k]+=f[j-1][k-t]*j/(n-j+1);
}
double ans=0;
for(int i=1; i<=n; ++i)
{
memcpy(g,f,sizeof f);
for(int j=1,t=A[i]; j<n; ++j)
for(int k=t; k<=b; ++k)
g[j][k]-=g[j-1][k-t]*j/(n-j+1);
for(int j=0,t=A[i]; j<n; ++j)
for(int k=0; k<=a && k+t<=b; ++k)
if(k+t>a) ans+=g[j][k]/(n-j);
}
printf("%.10f\n",ans);
return 0;
}
I. Gaokao √
找规律题。
#include <bits/stdc++.h>
#define pb emplace_back
#define pii pair<int, int>
#define fir first
#define sec second
#define ll long long
using namespace std;
#define gc() getchar()
inline int read()
{
int now=0,f=1; char c=gc();
for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now*f;
}
inline ll readll()
{
ll now=0,f=1; char c=gc();
for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now*f;
}
const int mod = 1e9+7;
inline int add(int a,int b){return a+b>=mod? a+b-mod: a+b;}
inline int sub(int a,int b){return a<b? a-b+mod: a-b;}
inline int mul(int a,int b){return 1LL*a*b%mod;}
int qpow(int a,int b){
int ret=1;
for(; b; b>>=1){
if(b&1) ret=mul(ret,a);
a=mul(a,a);
}
return ret;
}
inline int rev(int x){return qpow(x,mod-2);}
const int N = 1e3+10;
int fac[N], ifac[N];
void init(int n){
fac[0]=1;for(int i=1; i<=n; i++) fac[i]=mul(fac[i-1], i);
ifac[n]=rev(fac[n]); for(int i=n-1; i>=0; --i) ifac[i]=mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
return mul(fac[n], mul(ifac[m], ifac[n-m]));
}
int f[N][N];
void print(int x){
for(int i=7; i>=0; --i) cout<<((x>>i)&1);
}
ll cal(ll x){
ll ret=1;
for(; x; x>>=1) if(x&1) ret<<=1;
return ret;
}
int main(){
int T=read();
while(T--){
printf("%lld\n",cal(readll()-1));
}
return 0;
}
L. Landlord(DFS) √
\(Description\)
给定无限大平面上的两个矩形,求它们将平面分成几个连通块。
\(Solution\)
对给定的\(4\)个点离散化,然后直接DFS求连通块数。
离散化的时候对所得下标乘\(2\)再\(+1\)得到新下标,再标记\(vis=1\)会很方便。
//78ms 0KB
#include <bits/stdc++.h>
#define pc putchar
#define gc() getchar()
#define pb emplace_back
typedef long long LL;
const int N=20,Way[5]={1,0,-1,0,1};
int refx[N],refy[N],vis[N][N];
struct Point
{
int x1,y1,x2,y2;
}A[2];
inline int read()
{
int now=0,f=1; char c=gc();
for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now*f;
}
int Find(int *a,int x,int r)
{
int l=1,mid;
while(l<r)
if(a[mid=l+r>>1]<x) l=mid+1;
else r=mid;
return l*2+1;
}
void DFS(int x,int y)
{
vis[x][y]=1;
for(int i=0; i<4; ++i)
{
int xn=x+Way[i],yn=y+Way[i+1];
if(xn && yn && xn<N && yn<N && !vis[xn][yn]) DFS(xn,yn);
}
}
int main()
{
for(int T=read(); T--; )
{
for(int i=0; i<2; ++i) A[i]=(Point){read(),read(),read(),read()};
int tx=0,ty=0;
for(int i=0; i<2; ++i) refx[++tx]=A[i].x1, refx[++tx]=A[i].x2;
for(int i=0; i<2; ++i) refy[++ty]=A[i].y1, refy[++ty]=A[i].y2;
std::sort(refx+1,refx+1+tx), std::sort(refy+1,refy+1+ty);
tx=std::unique(refx+1,refx+1+tx)-refx-1;
ty=std::unique(refy+1,refy+1+ty)-refy-1;
memset(vis,0,sizeof vis);
for(int i=0; i<2; ++i)
{
int l1=Find(refx,A[i].x1,tx),r1=Find(refx,A[i].x2,tx);
int l2=Find(refy,A[i].y1,ty),r2=Find(refy,A[i].y2,ty);
for(int a=l1; a<=r1; ++a) vis[a][l2]=1, vis[a][r2]=1;
for(int b=l2; b<=r2; ++b) vis[l1][b]=1, vis[r1][b]=1;
}
int res=0;
for(int i=1; i<N; ++i)
for(int j=1; j<N; ++j)
if(!vis[i][j]) ++res, DFS(i,j);
printf("%d\n",res);
}
return 0;
}
很久以前的奇怪但现在依旧成立的签名
attack is our red sun $$\color{red}{\boxed{\color{red}{attack\ is\ our\ red\ sun}}}$$ ------------------------------------------------------------------------------------------------------------------------