NOIP集训题目解析
11.01
子段和
题目大意
给定一个长度为 \(n\) 的序列 \(a\) ,\(a_i=\{ -1,0,1 \}\) ,需要将 \(a\) 中的 \(0\) 变为 \(1\) 或 \(-1\) ,使得序列 \(a\) 的最大子段绝对值和最小。
\(1\le n \le 10^6\)
解析
考虑到何为最大子段绝对值和,其为 最大前缀和 \(-\) 最小前缀和
那么我们即需要将最小前缀和最大化的情况下让最大前缀和最小 或 让最大前缀和最小化的情况下让最小前缀和最大。
这里考虑第一种写法。
先让全部 \(0\) 变为 \(1\) ,求出最大最小前缀和,记为 \(x\) 。
那么考虑将一些 \(0\) 由 \(1\) 变为 \(-1\) , 这个 \(0\) 后的所有前缀和都变小 \(2\) ,如果最小前缀和 和 最大前缀和 都在这个 \(0\) 后,那么这个变化没有意义。
同理,将一个 \(0\) 由 \(1\) 变为 \(-1\) 如果不使其后的前缀和小于 \(x\) (影响最小前缀和),那么必定将其变为 \(-1\) 为最优情况(可能降低最大前缀和,不影响最小前缀和)。
考虑到前缀和每次只会减小 \(2\) ,所以对最小前缀和为 \(x-1\) 的情况再做一遍求解即可。
时间复杂度 \(O(n)\)
#include<bits/stdc++.h>
#define maxn 1001001
using namespace std;
char s[maxn];
int a[maxn],sum[maxn],mina[maxn],minx,maxx,b[maxn],ans=1e9,n,k;
int main(){
freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
scanf("%s",s+1);n=strlen(s+1);
for(int i=1;i<=n;i++){
if(s[i]=='-')a[i]=-1;
if(s[i]=='0')a[i]=0;
if(s[i]=='1')a[i]=1;
}
for(int i=1;i<=n;i++){
sum[i]=sum[i-1];
if(a[i]==-1)sum[i]--;
else sum[i]++;
}mina[n+1]=1e9;
for(int i=n;i>=1;i--)mina[i]=min(sum[i],mina[i+1]);
int k=0,x=mina[1];
for(int i=1;i<=n;i++){
if(a[i]!=0){b[i]=a[i]; continue;}
if(mina[i]+k>x+1)b[i]=-1,k-=2;
else b[i]=1;
}minx=1e9;maxx=0;
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+b[i];
if(sum[i]<minx)minx=sum[i];
if(sum[i]>maxx)maxx=sum[i];
}ans=min(ans,maxx-minx);
k=0;
for(int i=1;i<=n;i++){
if(a[i]!=0){b[i]=a[i]; continue;}
if(mina[i]+k>x)b[i]=-1,k-=2;
else b[i]=1;
}minx=1e9;maxx=0;
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+b[i];
if(sum[i]<minx)minx=sum[i];
if(sum[i]>maxx)maxx=sum[i];
}ans=min(maxx-minx,ans);
printf("%d\n",ans);
return 0;
}
序列
题目大意
给定一个长度为 \(n\) 的序列和两个整数 \(a\) 和 \(b\) ,初始序列全为 \(0\) ,每次操作可将连续 \(a\) 个位置的数变为 \(0\) ,或连续 \(b\) 个位置的数变为 \(1\) ,求最终序列有几种情况。答案对 \(10^9+7\) 取模
\(1\le n \le 5000\) ,\(1 \le a,b \le n\)
解析
11.02
幂
题目大意
给定 \(n\) 求 \((1^1+2^2+3^3+……+n^n)\ \ mod \ \ 2^{32}\)
\(1\le n \le 10^9\)
解析
打表(误
分块(误
分块打表(对
对于每段长度 \(10^7\) 的数求前缀和,打出一个长度为 \(10^2\) 的表
段内暴力跑。
#include<bits/stdc++.h>
using namespace std;
#define mod 4294967296
unsigned long long a[1011]={表};
int n,block,o;unsigned long long ans;
unsigned long long qpow(unsigned long long x,unsigned long long y){
unsigned long long ans=1;
while(y){
if(y&1){ans=ans*x;ans%=mod;}
x=(unsigned long long)x*x;x%=mod;y>>=1;
}return ans%mod;
}
int main(){
freopen("mi.in","r",stdin);
freopen("mi.out","w",stdout);
scanf("%d",&n);block=1000000;
ans=a[n/block];
for(int i=n/block*block+1;i<=n;i++)ans+=qpow(i,i),ans%=mod;
cout<<ans<<endl;;
}
最后的数
题目大意
$1\le n \le 10^6 $ , \(a_i=\{0,1,2\}\)
解析
显然最终答案的奇偶性很容易就能判断出来,奇数的话答案就是 \(1\) ,接下来考虑答案为偶数的情况。不难发现,如果某一时刻 \(a\) 中有 \(1\) ,那么操作后 \(a\) 中还有 \(1\) ,或是全为 \(0\) 。
所以只要 \(a\) 中有 \(1\) ,答案为 \(0\) 。
否则将 \(a\) 全体除以 \(2\) ,答案乘以 \(2\) ,再次利用上面的算法即可。
#include<bits/stdc++.h>
#define maxn 1000001
using namespace std;
char s[maxn];
int a[maxn],b[maxn];
int n,l,r,m;
int main(){
freopen("last.in","r",stdin);
freopen("last.out","w",stdout);
scanf("%d",&n);
scanf("%s",s);l=0;r=0;
for(int i=0;i<n;i++)a[i]=s[i]-'0';
for(int i=0;i<n;i++)if(((n-1)&i)==i)l+=(a[i]==1);
if(l&1){cout<<1;return 0;}
for(int i=0;i<n;i++)if(a[i]==1)r=1;
if(r){cout<<0;return 0;}
for(int i=0;i<n;i++)if(((n-1)&i)==i)m+=(a[i]==2);
if(m&1){cout<<2;return 0;}
cout<<0;
}
序列
题目大意
给定一个长度为 \(n\) 的序列 \(a\) 和一个正整数 \(x\)
求序列 \(a\) 有多少个非空子序列使得这个子序列中任意两个数的异或和大于等于 \(x\) .
\(1\le n \le 10^6\) ,\(0\le a_i,x \le 2^{60}\) ,保证 \(a\) 中元素互不相同。答案模 \(998244353\)
解析
一提到异或,可以往 \(Trie\) 树上想
考虑将序列 \(a\) 上的元素一一加入 \(Trie\) 中,每次统计当前元素的贡献。
遍历 \(Trie\) 时,对于当前位如果 \(x\) 为 \(1\) ,那么往反方向走,否则,累加反方向答案,往正方向走。
注意:单独一个元素也要累加进答案
#include<bits/stdc++.h>
#define maxn 6010001
#define mod 998244353
using namespace std;
long long trie[maxn][2],a[maxn],x,w[maxn];
long long ans,c;int n,tot=1;
void add(long long x,long long val){
int u=1;
for(long long i=59;i>=0;i--){
int v=((x>>i)&1);
if(!trie[u][v])trie[u][v]=++tot;
u=trie[u][v];w[u]+=val;w[u]%=mod;
}
}
long long query(long long x,long long y){
long long u=1,ans=0;
for(long long i=59;i>=0;i--){
if(!u)break;
int v=((x>>i)&1);
if((y>>i)&1)u=trie[u][v^1];
else ans+=w[trie[u][v^1]],ans%=mod,u=trie[u][v];
}ans+=w[u];ans%=mod;
return ans;
}
int main(){
freopen("seq.in","r",stdin);
freopen("seq.out","w",stdout);
scanf("%d%lld",&n,&x);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
sort(a+1,a+n+1);a[n+1]=-1;
for(int i=1;i<=n;i++){
long long q=(1+query(a[i],x))%mod;
add(a[i],q);ans+=q;c=1;ans%=mod;
}printf("%lld\n",ans);
return 0;
}
排列
题目大意
\(1\le n \le 12\) , $ 1\le|T|\le 10^6$
解析
瞎 jb pushdown即可
#include<bits/stdc++.h>
#define maxn 1000001
using namespace std;
int trie[maxn][3],ans[maxn],w[maxn],rev[maxn];
int qp[101];
int n,tot=1;
char s[maxn];
void add(int x){
int u=1;
for(int i=0;i<n;i++){
int v=(x/qp[i])%3;
if(!trie[u][v])trie[u][v]=++tot;
u=trie[u][v];
}w[u]=x;
}
void pushdown(int x){
if(!rev[x])return ;
swap(trie[x][1],trie[x][2]);
rev[trie[x][0]]^=1;
rev[trie[x][1]]^=1;
rev[trie[x][2]]^=1;
rev[x]=0;
}
void modify1(){
int u=1;
for(int i=0;i<n;i++){
pushdown(u);
swap(trie[u][1],trie[u][2]);
swap(trie[u][1],trie[u][0]);
u=trie[u][0];
}
}
void modify2(){rev[1]^=1;}
void print(int u,int dep,int S){
if(dep==n){ans[S]=w[u];return ;}
pushdown(u);
print(trie[u][0],dep+1,S);
print(trie[u][1],dep+1,S+qp[dep]);
print(trie[u][2],dep+1,S+qp[dep]*2);
}
int main(){
freopen("perm.in","r",stdin);
freopen("perm.out","w",stdout);
scanf("%d",&n);
scanf("%s",s+1);int slen=strlen(s+1);qp[0]=1;
for(int i=1;i<=n;i++)qp[i]=qp[i-1]*3;
for(int i=0;i<qp[n];i++)add(i);
for(int i=1;i<=slen;i++)if(s[i]=='1')modify1();else modify2();
print(1,0,0);
for(int i=0;i<qp[n];i++)printf("%d ",ans[i]);
return 0;
}
11.04
连续数
题目大意
给定 \(n\) ,求最小的 \(m\) 使得有 \(m(m\ge 2)\) 个连续正整数相加为 \(n\) ,无解输出 \(-1\)
\(1 \le n\le10^9\)
解析
用等差序列公式推一推即可。
#include<bits/stdc++.h>
using namespace std;
int n;bool flag;
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d",&n);
for(int i=2;i*i<=2*n;i++){
if(((2*n)%i==0)&&((2*n/i-i+1)%2==0)){
flag=1; cout<<i<<endl;
break;
}
}if(flag==0)cout<<-1;
return 0;
}
无向图
题目大意
给定一张 \(n\) 个点 \(m\) 条边的无向图,需要将每条边赋一个权值 \([0,4]\) ,满足对于每一个点,所有与它相连的边权值和为 \(5\) 的倍数。求方案数。可能有重边和自环。答案对 \(998244353\) 取模。
\(1\le n,m \le 10^6\)
解析
对于每个连通块分开讨论,答案为积。
考虑树的情况一定为 \(1\)
如果一个连通块内有奇环,那么答案为 \(5^{|E|-|V|}\)
否则答案为 \(5^{|E|-|V|+1}\)
证明待补……
用二分图染色判奇环即可
#include<bits/stdc++.h>
#define maxn 4100001
#define mod 998244353
using namespace std;
int cnt,from[maxn],to[maxn],head[maxn],Next[maxn],rd[maxn];
int n,m,u,v,vis[maxn],sume,sumv;bool flag;
long long ans;
long long qpow(long long x,long long y){
long long ans=1;
while(y){
if(y&1){ans=ans*x;ans%=mod;}
x=x*x;x%=mod;y>>=1;
}return ans%mod;
}
void add(int u,int v){
cnt++; rd[u]++;
from[cnt]=u; to[cnt]=v;
Next[cnt]=head[u]; head[u]=cnt;
}
void dfs(int u){sumv++;sume+=rd[u];
for(int i=head[u];i!=-1;i=Next[i]){
int v=to[i];
if(vis[v]!=-1){
if(vis[u]!=(vis[v]^1))flag=1;
continue;
}
vis[v]=(vis[u]^1);dfs(v);
}
}
int main(){
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
memset(head,-1,sizeof(head));
memset(vis,-1,sizeof(vis));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}ans=1;
for(int i=1;i<=n;i++){
if(vis[i]==-1){
flag=0;sume=0,sumv=0;
vis[i]=0; dfs(i);sume/=2;
if(flag==1){
ans=(ans*qpow(5,sume-sumv))%mod;ans%=mod;
}else{
ans=(ans*qpow(5,sume-sumv+1))%mod;ans%=mod;
}
}
}printf("%lld\n",ans);
return 0;
}
矩阵
题目大意
\(1\le n\le 500\)
解析
势能分析
每个点初始最短路最大为 \(n\) 级别
对于每次修改,用 \(DFS\) 从修改点进行松弛操作
点每次松弛,点的最短路必定减小
因为点最短路最大为 \(n\) ,所以只会被松弛 \(n\) 次
\(n^2\) 个点只会被松弛 \(n^3\) 次
所以暴力跑 \(DFS\) 即可
#include<bits/stdc++.h>
#define maxn 510
#define inf 1e9
using namespace std;
struct kkk{
int x,y;
};
int n,a[maxn*maxn];
int b[maxn][maxn],vis[maxn][maxn],dis[maxn][maxn];
int res,o,ans;
int tx[5]={0,1,-1,0,0},ty[5]={0,0,0,1,-1};
queue<kkk> q;
void BFS(){
for(int i=1;i<=n;i++)dis[i][n]=0,dis[n][i]=0,dis[1][i]=0,dis[i][1]=0,vis[i][n]=1,vis[n][i]=1,vis[1][i]=1,vis[i][1]=1;
for(int i=1;i<=n;i++)q.push((kkk){i,1});
for(int i=2;i<=n;i++)q.push((kkk){1,i});
for(int i=2;i<=n;i++)q.push((kkk){n,i});
for(int i=2;i<n;i++)q.push((kkk){i,n});
while(!q.empty()){
int ux=q.front().x,uy=q.front().y;q.pop();vis[ux][uy]=0;
for(int i=1;i<=4;i++){
int vx=ux+tx[i],vy=uy+ty[i];
if(dis[vx][vy]>dis[ux][uy]+1){
dis[vx][vy]=dis[ux][uy]+1;
if(!vis[vx][vy]){
vis[vx][vy]=1;
q.push((kkk){vx,vy});
}
}
}
}
}
void dfs(int ux,int uy){
for(int i=1;i<=4;i++){
int vx=ux+tx[i],vy=uy+ty[i];if(vx<=0||vy<=0||vx>n||vy>n)continue;
if(dis[vx][vy]>dis[ux][uy]+b[vx][vy]){
dis[vx][vy]=dis[ux][uy]+b[vx][vy];
dfs(vx,vy);
}
}
}
int main(){
freopen("rec.in","r",stdin);
freopen("rec.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)b[i][j]=1;
for(int i=1;i<=n*n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dis[i][j]=inf;
BFS();
for(int i=1;i<=n*n;i++){
int x=(a[i]+n-1)/n,y=a[i]-n*(x-1);
ans+=dis[x][y];dis[x][y]--;b[x][y]=0;
//dis[x][y]=max(0,dis[x][y]);
dfs(x,y);
}printf("%d",ans);
}
二元组
题目大意
对于 \(60\%\) 的分数 \(2\le n \le 5000\)
对于 \(100 \%\) 的分数 \(2\le n \le 2*10^5\)
\(1\le A_i,B_i\le10^9\)
解析
不是很会
60分就dp
//60分
#include<bits/stdc++.h>
#define maxn 5010
using namespace std;
int n,a[maxn],b[maxn];
long long f[maxn][maxn],ans;
int main(){
freopen("turple.in","r",stdin);
freopen("turple.out","w",stdout);
memset(f,0x3f,sizeof(f));ans=f[0][0];
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
}f[1][0]=0;f[0][1]=0;f[0][0]=0;
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
int k=max(i,j)+1;
if(i==j)continue;
if(k!=i) if(j!=0) f[i][k]=min(f[i][k],f[i][j]+abs(a[j]-a[k])+abs(b[j]-b[k])); else f[i][k]=min(f[i][k],f[i][j]);
if(k!=j) if(i!=0) f[k][j]=min(f[k][j],f[i][j]+abs(a[i]-a[k])+abs(b[i]-b[k])); else f[k][j]=min(f[k][j],f[i][j]);
}
}
for(int i=0;i<=n;i++)ans=min(ans,min(f[i][n],f[n][i]));
cout<<ans<<endl;
return 0;
}