noip模拟12
编号有些错乱aaa
早上打着哈欠走进机房,想着今天第一次和衡中联考,可不能挂
于是经过三个半小时的浴血奋战,终于爆成了暴力分:
考出了有史以来最差的成绩(虽然以前机房没有11个人)
考场上先乖乖地看完了所有题,曾经有那么一瞬间觉得 \(t3\) 好像比较可做,但是按照常理还是先搞 \(t1\) 吧……
\(t1\) 弄了一个小时觉得单调栈+主席树特别靠谱,但是显然并不会算复杂度
上厕所的时候曾经想到可以判断左右区间大小来节省时间,但是觉得这好像用处不大于是弃掉了……(事实证明rk1巨佬就是这样A掉的)
然后 \(t2\) 写了个暴搜开始迷信打表找规律
然后还有40分钟开始看 \(t3\),突然发现异常的可做,然后开始狂写,写啊写啊眼看着时间不够了,走后只得了个 \(puts\)("\(-1\)") 的暴力分……
A. 简单的区间
题目的柿子转化成:\(sum[i]-sum[j-1]=a[k] (\mod k)\)
可以用单调栈处理出每个数作为最大值的区间,枚举这个数k,枚举 \(i\),\(j\) 中的一个,这个根据哪边的点少枚举哪边,然后需要 \(log\) 找出另一半满足条件的 \(sum\) 的个数,这个相当于静态区间查找问题,可以用主席树解决
由于主席树常数巨大,chy想出了另一个处理办法,可以将每次查询都记录下来,即左右端点和值,然后利用容斥思想,再将区间转化成两部分,这样只需要查询1到x的信息即可
可以按位置排序,然后开桶不断加入新数,到达位置时更新答案
由于区间有 \(nlogn\) 个,相当于与主席树同阶,如果愿意可以桶排做到总复杂度 \(nlogn\)
这道题的正解给的是分治,每次将区间在区间最大值处分开,统计跨过最大值的点对数量,统计方法和前面说的一样
代码实现
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
const int maxn=2e6+5;
int n,k,a[maxn],sum[maxn],l[maxn],r[maxn],root[maxn],sta[maxn],tp,tot,vis[maxn];
long long ans;
struct Ask{
int pos,val,op;
Ask(){}
Ask(int x,int y,int m):pos(x),val(y),op(m){}
}ask[maxn*20];
bool cmp(Ask a,Ask b){
return a.pos<b.pos;
}
int main(){
n=read();
k=read();
for(int i=1;i<=n;i++){
a[i]=read();
sum[i]=(sum[i-1]+(a[i]%k))%k;
}
for(int i=1;i<=n;i++){
while(tp&&a[i]>a[sta[tp]])r[sta[tp]]=i-1,tp--;
l[i]=sta[tp]+1;
sta[++tp]=i;
}
while(sta[tp])r[sta[tp]]=n,tp--;
for(int i=1;i<=n;i++){
int x=a[i]%k;
if(i-l[i]>r[i]-i)
for(int j=i;j<=r[i];j++){
int y=(sum[j]-x+k)%k;
ask[++tot]=Ask(l[i]-2,y,-1),ask[++tot]=Ask(i-1,y,1);
}
else
for(int j=i-1;j>=l[i]-1;j--){
int y=(sum[j]+x)%k;
ask[++tot]=Ask(i-1,y,-1),ask[++tot]=Ask(r[i],y,1);
}
}
// for(int i=1;i<=tot;i++){
// cout<<ask[i].pos<<" "<<ask[i].val<<" "<<ask[i].op<<endl;
// }
sort(ask+1,ask+tot+1,cmp);
int tp=1;
while(ask[tp].pos<0&&tp<=tot)ans+=ask[tp].op*vis[ask[tp].val],tp++;
vis[0]++;
while(ask[tp].pos<=0&&tp<=tot)ans+=ask[tp].op*vis[ask[tp].val],tp++;
for(int i=1;i<=n;i++){
vis[sum[i]]++;
while(ask[tp].pos<=i&&tp<=tot)ans+=ask[tp].op*vis[ask[tp].val],tp++;
}
cout<<ans-n;
return 0;
}
B. 简单的玄学
确实是玄学……
这道题我连容斥都没想到
题目问出现相同数的个数,可以统计都不同的个数
相当于 \(n\) 个数的 \(m\) 排列,即 \(A_{2^n}^m\)
总方案数显然为 \({2^{n}}^m\)
然后考虑怎么计算:题目要求先约分再取模,这显然与先取模再约分不是一回事
如果要约分只能越好多好多个2
于是问题在于怎样统计出分子有多少个因子2
把分子拆开:\(2^n*(2^n-1)*(2^n-2)*(2^n-3)……(2^n-m+1)\)
上面每个括号里的2的次方数取决于后面减数里能提出多少的公因数,于是进一步问题转化为 \((m-1)!\) 里2的次数是多少
据说这是个经典的问题?
是这样计算的:首先将数除以2,累加到答案中,这一步相当于把奇数剔除,统计偶数个数,继续除以2,累计答案,这一步相当于把是2的倍数不是4的倍数的剔除,统计4的倍数的个数,以此类推
统计完这个就可以愉快的约分了
对于分子提出公因数后还剩下多少——这道题的模数看着是不是特别的神奇?这是精心设计过的,因为分子是连续的m个数,当m>mod时,那么当中必有一个是 mod 的倍数,分子就是0了,m较小的时候直接暴力计算即可
代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,cnt,x,y,tp,mul;
const int mod=1e6+3;
int po(int a,int b=mod-2){
int ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
signed main(){
cin>>n>>m;
int sum=m-1;
while(sum){
sum>>=1ll;
cnt+=sum;
}
cnt+=n;
x=y=po(po(2,n),m)%mod;
if(m<mod){
tp=po(2,n);
mul=1;
for(int i=1;i<=m;i++){
mul=mul*tp%mod;
tp--;
if(tp<0)tp+=mod;
}
x=(x-mul+mod)%mod;
}
x=x*po(po(2ll,cnt))%mod;
y=y*po(po(2,cnt))%mod;
cout<<x<<" "<<y<<endl;
return 0;
}
C. 简单的填数
这道题算是本场考试思路最正确的一道题了,只不过这种方法代码写起来比较恶心~
用 \(f[i][j]\) 表示第 i 个已知数是第 j 个连续数是否可行,这是一个布尔变量,第二维开 [1,5]
\(f[i][j]\) 要从 \(f[i-1][k]\) 转移,转移条件是这样的:首先统计出两点之间没出现的颜色数,并处理出安排好这些颜色的最小区间长度和最大区间长度,如果它们之间的距离在这个范围内则可以转移。最小值为颜色数乘2,如果 \(k==1\) 加1,最大值为颜色数乘5加 \(5-k\),并且都要加上 \(j-1\)
这样可以统计出最后一个出现的位置是第几个可行,分两种情况讨论,如果大于等于2,则从下一个开始颜色不同,并每个颜色都有两个(当然要特判一下最后一种颜色也要有匹配);只能等于1的话则下一个必须染一样的颜色,之后再按上面的做
至于构造方案,转移是记录当前状态是由哪个状态转移而来的,现将两端点的要求满足,中间每种颜色都安排两个位置,如果位置还有剩余,不超过5个随便安排即可。
代码实现
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
const int maxn=2e5+5;
int n,a[maxn],tot,b[maxn],ans[maxn],sta[maxn],tp;
bool f[maxn][10];
pair<int,int>from[maxn][10];
int main(){
n=read();
a[++tot]=1;
for(int i=1;i<=n;i++){
b[i]=read();
if(i!=1){
if(b[i])a[++tot]=i;
}
}
b[1]=1;
f[1][1]=true;
for(int i=2;i<=tot;i++){
int j=i-1;
int num=a[i]-a[j]-1;
int c=b[a[i]]-b[a[j]]-1;
for(int k=1;k<=5;k++){
if(f[j][k]){
if(c==-1){
if(k+num+1<=5){
f[i][k+num+1]=true;
from[i][k+num+1]=make_pair(j,k);
}
}
for(int p=1;p<=5;p++){
if((p-1+2*c+((k==1)?1:0))<=num&&p-1+5*c+5-k>=num){
f[i][p]=true;
from[i][p]=make_pair(j,k);
}
}
}
}
}
int flag=0;
for(int k=5;k>=2;k--){
if(f[tot][k]){
flag=k;
break;
}
}
if(flag){
cout<<(n-a[tot])/2+b[a[tot]]<<endl;
ans[n]=(n-a[tot])/2+b[a[tot]];
int q=b[a[tot]]+1,last=a[tot];
for(int i=a[tot]+1;i<=n-1;i+=2)ans[i]=ans[i+1]=q,q++,last=i+1;
for(int i=last+1;i<=n-1;i++)ans[i]=ans[n];
for(int i=tot,j=flag;i>=1;i--){
ans[a[i]]=b[a[i]];
if(i==1)break;
int x=i-1;
int y=from[i][j].second;
int num=a[i]-a[x]-1;
tp=0;
for(int k=1;k<=j-1;k++)sta[++tp]=b[a[i]],num--;
for(int k=b[a[x]]+1;k<=b[a[i]]-1;k++){
sta[++tp]=k;
sta[++tp]=k;
num-=2;
}
if(num){
if(y==1)sta[++tp]=b[a[x]],num--;
if(num)
for(int k=b[a[x]]+1;k<=b[a[i]]-1;k++){
sta[++tp]=k;
num--;
if(!num)break;
sta[++tp]=k;
num--;
if(!num)break;
sta[++tp]=k;
num--;
if(!num)break;
}
}
if(num){
for(int k=1;k<=num;k++)sta[++tp]=b[a[x]];
}
// cout<<tp<<" "<<a[i]<<" "<<a[x]<<endl;
sort(sta+1,sta+tp+1);
for(int k=a[i]-1;k>=a[x]+1;k--)ans[k]=sta[tp--];
j=y;
}
}
else{
if(f[tot][1]){
// cout<<"hhh ";
cout<<(n-a[tot]-1)/2+b[a[tot]]<<endl;
ans[n]=(n-a[tot]-1)/2+b[a[tot]];
if(a[tot]!=n-1){
int q=b[a[tot]]+1,last=a[tot]+1;
ans[a[tot]]=b[a[tot]];
for(int i=a[tot]+2;i<=n-1;i+=2)ans[i]=ans[i+1]=q,q++,last=i+1;
for(int i=last+1;i<=n-1;i++)ans[i]=ans[n];
}
// cout<<"hhh "<<tot<<endl;
for(int i=tot,j=1;i>=1;i--){
ans[a[i]]=b[a[i]];
if(i==1)break;
int x=i-1;
int y=from[i][j].second;
int num=a[i]-a[x]-1;
// cout<<"ppp "<<i<<" "<<j<<endl;
tp=0;
for(int k=1;k<=j-1;k++)sta[++tp]=b[a[i]],num--;
for(int k=b[a[x]]+1;k<=b[a[i]]-1;k++){
sta[++tp]=k;
sta[++tp]=k;
num-=2;
}
// cout<<num<<endl;
if(num){
if(y==1)sta[++tp]=b[a[x]],num--;
if(num)
for(int k=b[a[x]]+1;k<=b[a[i]]-1;k++){
sta[++tp]=k;
num--;
if(!num)break;
sta[++tp]=k;
num--;
if(!num)break;
sta[++tp]=k;
num--;
if(!num)break;
}
}
if(num){
for(int k=1;k<=num;k++)sta[++tp]=b[a[x]];
}
sort(sta+1,sta+tp+1);
for(int k=a[i]-1;k>=a[x]+1;k--)ans[k]=sta[tp--];
j=y;
}
}
else {
cout<<-1<<endl;
return 0;
}
}
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
return 0;
}