2023年10月17日 / 信息学,比赛,2023NOIPA层联测
题目链接
注意到 n n n为偶数,考虑求 s s s表示 b b b的异或和,因为每个人看不到自己的 a a a,所以其的 a a a一定在在 s s s中被计算了奇数次,对于异或,其实就是一次。所以 s s s就是所有人 a a a的异或和,那么每个人的 a = s ⊕ b a=s \oplus b a=s⊕b。
#include
using namespace std;
const int maxn=2*1e5;
int n;
int a[maxn+5];
int sum;
signed main()
{
freopen("hat.in","r",stdin);
freopen("hat.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),sum^=a[i];
for(int i=1;i<=n;i++)
printf("%d ",sum^a[i]);
return 0;
}
考虑动态规划,设 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示第 i i i个单词与/不与前面的交换。
有转移:
初始值:
f [ 0 ] [ 0 ] = f [ 1 ] [ 0 ] = 1 f[0][0]=f[1][0]=1 f[0][0]=f[1][0]=1
#include
using namespace std;
#define int long long
const int maxn=1e5;
const int maxm=2*1e6;
const int mod=1e9+7;
int n,m;
string s[maxn+5];
int f[maxn+5][2];
signed main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;i++)
cin>>s[i];
f[0][0]=f[1][0]=1;
for(int i=2;i<=n;i++)
{
f[i][0]=(f[i-1][0]+f[i-1][1])%mod;
if(s[i]!=s[i-1])
f[i][1]=(f[i-2][0]+f[i-2][1])%mod;
}
printf("%lld",(f[n][0]+f[n][1])%mod);
return 0;
}
洛谷:P5987
首先显然易见的是横纵坐标完全可以分开考虑(因为横坐标对纵坐标没有影响),只需要分别对横纵求答案,然后相乘就是最后的答案。
接着问题转化成 [ L , R ] [L,R] [L,R]与 [ 0 , L ) , ( R , X ] [0,L),(R,X] [0,L),(R,X]选一个最后求交集最大的问题。
我们将所有的 L , R L,R L,R放在一起排个序,相邻的两个点会分割若干条线段(区间),其中每一条线段对于每一对 L , R L,R L,R选择(也就是选$ [ L , R ] [L,R] [L,R]与 [ 0 , L ) , ( R , X ] [0,L),(R,X] [0,L),(R,X])是固定的。所以我们可以得到每一条线段的需求。
那么需求相同的线段就可以同时取到,对答案的贡献也就是他们长度的和。
问题是如何记录需求。我们给 [ L i , R i ] [L_i,R_i] [Li,Ri]这一选择一个特殊数 p i ∈ [ 0 , 2 64 ) p_i \in [0,2^{64}) pi∈[0,264)(随机即可,注意获取随机数的方式,因为这会影响随机数的范围), [ 0 , L i ) , ( R i , X ] [0,L_i),(R_i,X] [0,Li),(Ri,X]这一个选择记为 0 0 0。接着我们发现对于 [ L i , R i ] [L_i,R_i] [Li,Ri]之间的线段,该区间对其的贡献都为 p i p_i pi而其他的都为 0 0 0,所以使用一个异或差分(实现参考代码)即可解决问题。再将一个线段对应的选择求异或和就可以代表他的需求。
为什么这是对的?因为我们做的是异或运算所以需求 q i ∈ [ 0 , 2 64 ) q_i \in [0,2^{64}) qi∈[0,264)并且是随机分布的。我们可以使用生日悖论
来计算正确的概率。
可以将其转换成有 m = 2 n + 1 m=2n+1 m=2n+1个小朋友,在 2 64 2^{64} 264天一年的情况下,所有小朋友生日不同的概率(记 a = 2 64 a=2^{64} a=264)。
P = a ! a m ( a − m ) ! > a − m a P=\frac{a!}{a^m(a-m)!}>\frac{a-m}{a} P=am(a−m)!a!>aa−m
极限数据下后者约等于 0.9999999999999457898913757247783 0.9999999999999457898913757247783 0.9999999999999457898913757247783,而 P P P更要大于此,可以说是非常优秀了。
#include
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define piu pair<int,ull>
#define fi first
#define se second
const int maxn=5*1e5;
int n,X,Y;
pii a[maxn+5],b[maxn+5];
vector<piu> E,S;
mt19937_64 rnd(random_device{}());
inline bool CmpE(piu x,piu y){return x.fi<y.fi;}
inline bool CmpS(piu x,piu y){return x.se<y.se;}
inline int Solve(pii f[],int val)
{
ull tmp;
E.clear();
for(int i=1;i<=n;i++)
{
tmp=rnd();
E.push_back(piu(f[i].fi,tmp));
E.push_back(piu(f[i].se,tmp));
}
E.push_back(piu(0,0));
E.push_back(piu(val,0));
sort(E.begin(),E.end(),CmpE);
tmp=0;
S.clear();
for(int i=0,vtp=E.size();i<vtp-1;i++)
{
tmp^=E[i].se;
if(E[i].fi<E[i+1].fi)
S.push_back(piu(E[i+1].fi-E[i].fi,tmp));
}
sort(S.begin(),S.end(),CmpS);
tmp=0;
int sum=0,res=0;
for(int i=0,vtp=S.size();i<vtp;i++)
{
if(tmp==S[i].se)
sum+=S[i].fi;
else
tmp=S[i].se,sum=S[i].fi;
res=max(res,sum);
}
return res;
}
signed main()
{
freopen("globe.in","r",stdin);
freopen("globe.out","w",stdout);
int x,xx,y,yy;
scanf("%d%d%d",&n,&X,&Y);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&x,&y,&xx,&yy);
if(x>xx) swap(x,xx);
if(y>yy) swap(y,yy);
a[i]=pii(x,xx);
b[i]=pii(y,yy);
}
printf("%lld",1ll*Solve(a,X)*Solve(b,Y));
return 0;
}
正解需要笛卡尔树(正在学),这里提供一个非正解。
考虑一个拼盘,分极差(最大值减最小值)小于 30 30 30和大于两种情况。
因为极差小于 30 30 30,所以我们把每一个 a a a减去最小值,然后暴力枚举 i , j i,j i,j表示考虑到第 i i i位,并维护一个前缀和,最终答案为 2 j 2^j 2j,我们直接在set
里搜索 s u m − 2 j sum-2^j sum−2j即可。
我们同样先枚举 i i i,然后枚举一个 j j j从 i i i往前搜索。然后我们像 50 p t s 50pts 50pts暴力一样做,每次暴力插入,并使用set
维护幂集合(参见代码)。现在,考虑减枝。
首先,如果集合内的极差减去大小已经大于 j j j那么接下来的枚举就没有必要了,因为无论怎么插入,都不可能归成一个数。
时间复杂度无法分析,对于没有特殊构造的数据能过。
#include
using namespace std;
#define int long long
const int maxn=2*1e5;
const int maxm=100;
int n;
int a[maxn+5];
int ans;
set<int> se;
set<int>::iterator it;
int x;
int mx=INT_MIN,mn=INT_MAX;
inline void add(int x)
{
while(true)
{
it=se.find(x);
if(it==se.end())
{
se.insert(x);
break;
}
else
se.erase(it),x++;
}
}
inline void Work1()
{
se.insert(0);
for(int i=1;i<=n;i++) a[i]-=mn;
int sum=0;
for(int i=1;i<=n;i++)
{
sum+=(1<<a[i]);
for(int j=1;j<=sum;j<<=1)
ans+=se.count(sum-j);
se.insert(sum);
}
}
inline void Work2()
{
for(int i=n;i>=1;i--)
{
se.clear();
for(int j=i;j>=1;j--)
{
add(a[j]);
if(se.size()==1)
ans++;
if(*se.rbegin()-*se.begin()-(int)se.size()>j)
break;
}
}
}
signed main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
mx=max(mx,a[i]);
mn=min(mn,a[i]);
}
if(mx-mn<=30) Work1();
else Work2();
printf("%lld",ans);
return 0;
}