noip模拟22
今天是用 2020 年的 NOIP 模拟的。T2 被换掉了,因为之前专题做过,然后换了个更好做的?
也是有了自己的 200+。
ps:没有按难度排序。
A [NOIP2020] 微信步数
B [NOIP2020] 移球游戏
神秘构造题,但其实思路很简单。
考虑一种接口化,或者说模块化的解法,使得我能利用一些柱子实现某一个数只出现在某一个柱子上。
打个比方,这就像魔方一样,可以构造一个特定的公式,使得一些数交换位置而其他数不受影响。
Step 1 制造全 \(0\) 列
首先,假设当前要处理的元素是 now,那我们就把所有 now 标记成 \(1\),其他标记成 \(0\)。
然后考虑一个事情,如何把一个序列的 \(0\) 和 \(1\) 分开?
记第 \(i\) 根柱子上的 \(i\) 的个数为 \(tot_i\),我们可以先把第 \(n\) 个柱子上的前 \(tot_i\) 个移动到 \(n+1\)(空柱子)上
然后将第一列的 \(1\) 挪到第 \(n\) 列,其他 \(0\) 挪到第 \(n+1\) 列。
将第 \(n+1\) 列的前 \(m-tot_i\) 个 \(0\) 挪回第 \(1 列\)
然后,将第 \(2\) 列的 \(0\) 分裂到第 \(1\) 列和第 \(n+1\) 列。
然后就会发现第 \(2\) 列没有数了,第 \(1\) 列成了全 \(0\) 列。
注意到原本定义的列数发生错位了,我们可以用 \(p_i\) 标记 \(i\) 列的原始编号。这样在进行完上述操作之后就需要交换 \(1\) 和 \(n\) 的编号和 \(2\) 和 \(n+1\) 的编号。
Step 2 构造全 \(1\) 列
我们在上述操作中已经构造出来一个全 \(0\) 的序列,并已经使得 \(1\) 号柱子上的全部 \(1\) 都到了 \(n\) 号柱子的最上方。重复以上过程就会使全部的 \(1\) 出现在所有序列上方,把它放到那个空序列就行。
处理 \(n=2\) 的情况
其实很简单,和上述思路类似,把前 \(tot\) 的放在 \(3\) 号,把 \(1\) 号柱子分裂在 \(2\) 号和 \(3\) 号上,再把分裂后的回填回 \(1\),这样 \(1\) 柱子就上下分层了,把 \(3\) 填到 \(2\) 上,就好了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=401;
int stk[51][N],top[51];
int tot[51];
vector<pair<int,int> >ans;
int p[51];
//
//void calc(int col,int a,int b,int c,int d)
//{
// for(int i=1;i<=tot[p[a]];i++)
// {
//
// }
//}
inline void move(int a,int b)
{
ans.push_back({a,b});
stk[b][++top[b]]=stk[a][top[a]--];
}
int m;
inline int tp(int a)
{
return stk[a][top[a]];
}
signed main()
{
freopen("ball.in","r",stdin);
freopen("ball.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
p[i]=i;
for(int j=1;j<=m;j++) cin>>stk[i][j];
top[i]=m;
}
p[n+1]=n+1;
top[n+1]=0;
for(int now=n;now>=3;now--)
{
int tmp=0;
for(int k=1;k<=m;k++)tmp+=(stk[p[1]][k]==now);
for(int i=1;i<=tmp;i++)move(p[now],p[now+1]);
for(int i=1;i<=m;i++)
{
if(tp(p[1])==now) move(p[1],p[now]);
else move(p[1],p[now+1]);
}
for(int i=1;i<=m-tmp;i++) move(p[now+1],p[1]);
for(int i=1;i<=m;i++)
{
if(tp(p[2])==now||top[p[1]]==m) move(p[2],p[now+1]);
else move(p[2],p[1]);
}
swap(p[1],p[now]),swap(p[2],p[now+1]);
for(int k=1;k<now;k++)
{
tmp=0;
for(int j=1;j<=m;j++)tmp+=(stk[p[k]][j]==now);
for(int i=1;i<=tmp;i++)move(p[now],p[now+1]);
for(int i=1;i<=m;i++)
{
if(tp(p[k])==now) move(p[k],p[now]);
else move(p[k],p[now+1]);
}
swap(p[k],p[now+1]);
swap(p[k],p[now]);
}
for(int i=1;i<now;i++) while(tp(p[i])==now) move(p[i],p[now+1]);
for(int i=1;i<now;i++) while(top[p[i]]<m) move(p[now],p[i]);
}
// cerr<<ans.size()<<"\n";
int tmp=0;
for(int k=1;k<=m;k++)tmp+=(stk[p[1]][k]==1);
for(int i=1;i<=tmp;i++)move(p[2],p[3]);
for(int i=1;i<=m;i++)
{
if(tp(p[1])==1) move(p[1],p[2]);
else move(p[1],p[3]);
}
for(int i=1;i<=tmp;i++) move(p[2],p[1]);
for(int i=1;i<=m-tmp;i++) move(p[3],p[1]);
while(top[p[3]]) move(p[3],p[2]);
while(tp(p[1])==2) move(p[1],p[3]);
for(int i=1;i<=m;i++)
{
if(tp(p[2])==1) move(p[2],p[1]);
else move(p[2],p[3]);
}
cout<<ans.size()<<"\n";
for(auto i:ans) cout<<i.first<<" "<<i.second<<"\n";
}
C [NOIP2020] 排水系统
这才是真正的 T1。开考半个小时切了。
按题意模拟即可。但是会爆 long long,这给我提了个醒,当大样例答案特别大,但是没超 long long 的时候,一定要自己再搓一个更大的样例,看看会不会超范围。如果超了果断换 int128 或者写高精。其实有个更冒险的做法是用 long double,虽然存储范围很大,但是越大精度越低。
所以还是用 int128 比较好。
后记:题目没卡深搜,其实最正确的做法是拓扑排序,遇到入度为 \(0\) 的点就放水,直到最后。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int __int128
int n,m;
const int N=1e5+5;
vector<int>e[N];
int indeg[N],outdeg[N];
int ansa[N],ansb[N];
int lcm(int x,int y)
{
if(x==0||y==0) return 1;
return x/__gcd(x,y)*y;
}
inline void add(int u,int a,int b)
{
int lc=lcm(ansb[u],b);
ansa[u]=ansa[u]*(lc/ansb[u]);
ansb[u]=lc;
a=a*(lc/b);
ansa[u]+=a;
int gc=__gcd(ansa[u],ansb[u]);
ansa[u]/=gc,ansb[u]/=gc;
}
void dfs(int u,int a,int b)
{
for(int v:e[u])
{
if(!outdeg[v])
add(v,a,b*outdeg[u]);
dfs(v,a,b*outdeg[u]);
}
}
inline int read()
{
register int s=0;
register char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') s=(s<<1)+(s<<3)+(c^48),c=getchar();
return s;
}
void write(int x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
signed main()
{
freopen("water.in","r",stdin);
freopen("water.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;i++)
{
ansb[i]=1;
int d=read();
for(int j=1;j<=d;j++)
{
int v=read();
outdeg[i]++,indeg[v]++;
e[i].push_back(v);
}
}
for(int i=1;i<=n;i++)
{
if(indeg[i]==0) dfs(i,1,1);
}
for(int i=1;i<=n;i++)
{
if(!outdeg[i]) write(ansa[i]),putchar(' '),write(ansb[i]),putchar(10);
}
}
D 三元组
\(O(n^3)\) 的暴力是好想的,\(O(nk)\) 的性质是好做的。
merge 起来就有 \(60\) 分。
设 \(dp[i][j]\) 表示到第 \(i\) 个数时 \(a+b^b\)(其中 \(a\in[1,i]b\in[a,i]\))余数为 \(k\) 的数量。因为每新加入一个 \(i\) 就相当于新加入点对 \((1,i),(2,i)\cdots (i,i)\),也就是所有 \(i^2+1\) 到 \(i^2+i\) 模 \(k\) 余数的个数加一。\(dp[i][j]=dp[i-1][j]+b[j]\),其中 \(b[j]\) 表示新加入点对中余数为 \(j\) 的个数。答案就是 \(\displaystyle \sum_{i=1}^n dp[i][i^3 \text{ mod }k]\)。
可以滚一下,降低空间复杂度。
然后发现可以拿树状数组维护值域区间 \([0,k)\),就是每次在上述区间上区间加 \(1\),并给重复的整块加上 \(\lfloor \frac i k\rfloor\),用区修单查树状数组就实现了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int n,k;
int *c;
struct SegTree{
inline int lowbit(int x)
{
return x&-x;
}
void update(int x,int K)
{
x++;
for(;x<=k;x+=lowbit(x)) c[x]+=K;
}
void Update(int l,int r,int K)
{
update(l,K),update(r+1,-K);
}
int query(int x)
{
x++;int res=0;
for(;x;x-=lowbit(x)) res+=c[x];
return res;
}
}st;
void q()
{
cin>>n>>k;
c=new int [k+10];
for(int i=0;i<=k+9;i++) c[i]=0;
int ans=0,tmp=0;
for(int i=1;i<=n;i++)
{
int l=(i*i+1),r=i*(i+1);
l%=k,r%=k;
tmp+=i/k;
if(i%k)
{
if(l>r) st.Update(0,r,1),st.Update(l,k-1,1);
else st.Update(l,r,1);
}
ans+=st.query((i*i%k*i)%k)+tmp;
}
cout<<ans<<"\n";
}
signed main()
{
freopen("exclaim.in","r",stdin);
freopen("exclaim.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;cin>>T;
for(int i=1;i<=T;i++) cout<<"Case "<<i<<": ",q();
}