赛·码·niang(not impotant and not good)
模拟赛题解以后就发在这里了
2023.10.5
CSP模拟49
ltx这场太恶心了,生理上的那种,看见要模一个大质数我就知道这题要完,这种题连暴力都tmd没法打,随便糊点分出来算了
T1 模板题
额……过
T2 THUSC
排序,两个分值,分值确定,但是分值的权值不定,让求求在不同权值下有多少种排名
我¥%&…………#%¥……&()&……()……¥
咱就不能好好排名吗,你把权值给了也行啊
(胡思乱想开始)
首先我们来考虑权值的变动是怎么具体影响排名的,举个例子
现在A两项分值是1 50,B两项分值是5 1
然后我给第一项一个13的权值,再给第二项一个1的权值
那A现在是13 50,B是65 1
这个13是咋得出来的,是你(50-1)/(5-1)得出来的
也就是说,出现影响的权值比为 \(abs(x_1-x_2)/abs(y_1-y_2)\)
那这个就可以提供一个暴力的思路,因为前几个数据点\(x\)和\(y\)的值最大也就是个\(100\),也就是说这个权值比只有\(1e4\)种情况,我们直接暴力枚举权值比,算出不同的结果再去重,可以捞一点点分
去重操作直接hash得了
2023.10.6
NOIP A层联测6(赛时版本)
T1 万花筒
人话题意:给出一个无向带权图\(G\),对于第\(i\)个\(G\)来说,根据边\((u,v,w)\)转化为\(((u+i)mod\ n+1,(v+i)mod\ n+1,w)\)的规则,得到一个新的图,求这个图的最小生成树的权值和
数据范围:多测数据\(T<=100\),\(n,w<=10^9\),\(m\)之和\(<=10^5\)
最多\(1e5\)条边,应该算法上就是\(O(nlogn)\)
先手模一遍小样例
额,这个边是越来越多的
随便糊一个暴力上去,根据规则推出新边,并存入队列,将边用map记录,重边我们就不向下推,这样建图后直接跑最小生成树
(就是清空挺费时间的)
T2 冒泡排序趟数期望
打表得出来一个规律
然后呢
然后……就没了
T3 数点
k=1暴力分应该挺好写的
(T2打表没时间写了)
NOIP A层联测6(赛后版本)
T1 万花筒
一个类似于贪心的思想,首先我们按照 \(d=abs(u-v)\) 对所有边分类,然后我们按照边权从小到大排序,根据题意我们可以知道,最终 \(i,i+d,i+2*d...\) 这些边会连接在一起,而不在这个范围内的边有 \(gcd(n,d)\) 个, \(ans+=(n-gcd(n,d))*w\) ,然后就将 \(n\) 更新为 \(n-gcd(n,d)\) ,当 \(n==1\) 停止,这时就得到了最小生成树
爱看不看,不看滚
#include<bits/stdc++.h>
#define int long long
using namespace std;
int gcd(int x,int y){
if(y) return gcd(y,x%y);
else return x;
}
struct edge{
int arr,w;
bool operator <(const edge &x)const{
return w<x.w;
}
};
vector<edge> line;
int t,n,m;
int ans=0;
signed main(){
freopen("kaleidoscope.in","r",stdin);
freopen("kaleidoscope.out","w",stdout);
cin>>t;
while(t--){
line.clear();
ans=0;
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
int turn=abs(u-v);
line.push_back(edge{turn,w});
}
sort(line.begin(),line.end());
int now=n;
for(edge i:line){
int x=gcd(i.arr,now);
if(x==now)
continue;
ans+=(now-x)*i.w;
now=x;
if(x==1)
break;
}
cout<<ans<<endl;
}
}
T2 冒泡排序趟数期望
对于冒泡排序来说,每一趟都会让所在位置大于正确位置的数左移一位
那我们就知道了,对于一个 \(n\) 个数的随机序列,它的移动趟数就应该是 \(max(site[i]-i)\),\(site[i]\) 是 \(i\) 在此序列的初始位置
So,我们设该序列的移动趟数为\(k\),
2023.10.7
CSP模拟50(赛时版本)
T1 异或
没啥好说的结论题,规律打表就能看出来
直接贴代码了
爱看不看,不看滚
#include<bits/stdc++.h>
#define int long long
using namespace std;
bool flag=true;
int now=0,ans=0,tot=0,m,n;
void solve(){
now++;
if(n%2!=0){
if(flag){
n=n/2+1;
tot+=n;
ans+=now*n;
flag=false;
}
else{
n=n/2;
tot+=n;
ans+=now*n;
flag=true;
}
}
else{
n=n/2;
tot+=n;
ans+=now*n;
}
if(m==tot)
return;
solve();
}
signed main(){
cin>>n;
m=n;
solve();
cout<<ans;
return 0;
}
T2 赌神
题没读明白,你筹码呢?
不是你筹码呢,你初始筹码不给我用啥啊
你总不能筹码数随意取吧,那你都有无限的筹码了,你还赌个屁啊(大雾
样例解释:
假设颜⾊为红⾊和蓝⾊。⼀开始你有\(1\)个筹码,红球和蓝球都有\(2\)个。你将在两个球上都押\(\frac{1}{2}\)的筹
码,幕后⿊⼿会控制红球掉落,此时你获得了\(2* \frac{1}{2}=1\)个筹码,此时你拥有\(1\)个筹码,红球和蓝球
分别有\(1,2\)个;
接下来,你将在红球上押\(\frac{1}{3}\)个筹码,在蓝球上押\(\frac{2}{3}\)个筹码,幕后⿊⼿会控制蓝球掉落,你将获得
\(2* \frac{2}{3}= \frac{4}{3}\)个筹码,此时你拥有\(\frac{4}{3}\)个筹码,红球和蓝球分别有\(1,1\)个;
然后,你将在红球上押\(\frac{2}{3}\)个筹码,在蓝球上押\(\frac{2}{3}\)个筹码,幕后⿊⼿会控制蓝球掉落,你将获得\(2* \frac{2}{3}= \frac{4}{3}\)
个筹码,此时你拥有\(\frac{4}{3}\)个筹码,红球和蓝球分别有\(1,0\)个;
最后,你把所有筹码全部押在红球上,幕后⿊⼿控制红球掉落,你获得了\(2* \frac{4}{3}= \frac{8}{3}\)个筹码,此时你
拥有了\(\frac{8}{3}\)。
红球与蓝球全部掉落完毕,游戏结束,你最后得到了\(\frac{8}{3}\)\(个筹码。可以证明,\)\frac{8}{3}$$是你能得到的最⼤收益。
T3 路径
大致读了一下,需要求出树上任意两点距离的\(k\)次方,再求和
求树上任意两点距离这个很套路了,是换根dp
我们考虑用\(map\)将所有的距离存起来,然后直接统计个数,快速幂求答案加起来就行了
换根dp时间复杂度是\(O(n)\)的,map遍历\(O(n)\),快速幂\(O(logn)\)
总时间复杂度\(O(n)\)级别(这不纯纯扯淡吗)
给了\(3s\)的时限,\(1e6\)的数据,\(std\)肯定是\(O(nlogn)\)级别的啊
那问题出现在哪?
换根dp
换根dp能\(O(n)\)解决的问题,是求整棵树任意点对距离之和,它换根以后,根到其他点的距离和是由上一个根的距离和用转移方程\(O(1 )\)转换出来的
但是现在我们要求的不是距离和,而是单个的所有距离,这就麻烦起来了
得,换根dp假了
不过先写一份伪代码(换根dp咋打我是真忘了)
这个dp还不如直接遍历打暴力……
爱看不看,不看滚
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+10;
int n,k;
struct edge{
int to,next;
}e[maxn<<1];
int head[maxn],cnt;
int qpow(int x,int y){
int res=1;
while(y){
if(y&1)res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
void add(int u,int v){
e[++cnt].next=head[u];
e[cnt].to=v;
head[u]=cnt;
}
map<int,int>mp;
void dfs(int x,int fa){
siz[x]=1;
dep[x]=dp[fa]+1;
for(int i=head[x];i;i=e[i].next){
int to=e[i].to;
if(to==fa)
continue;
dfs(to,x);
siz[x]+=siz[to];
}
}
void turn_dp(int x,int fa){
for(int i=head[x];i;i=e[i].next){
int to=e[i].to;
if(to==fa)
continue;
rand(son(x))--;//x的子树内所有点对应距离减一
rand(other(son(x)))++;//其余点加一
map[rand(all)]++;//将新距离存储
turn_dp(to,x)
}
}
signed main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs(1,0);
turn(1,0);
int ans=0;
for(int i=1;i<=n;i++){
ans=(ans+qpow(i,k)*map[i]%mod)%mod;
}
cout<<ans;
}
T4 树
数据范围\(3e5\),给了足足\(6s\)的时限,树形结构,修改加查询,猜测这道题正解是树分块(对,我还是不会打)
2023.10.9
NOIP A层联测8(赛时版本)
T1 集合
浅浅打个表(对,我又开始打表了)
然后发现了一个规律,得到了一个转移方程
我们设\(dp[i]\)代表子集和为\(i\)子集数量
然后我们枚举当前子集的最大值
那么就可以得到$$dp[j]=dp[j]+dp[j-i]$$
\(j\)由\(i\)来确定,不难发现\(j\)的取值范围是\([i,i*(i+1)/2]\)
也就是当前子集只包含\(i\)到包含\((1,i)\)
可以发现,下标较小的\(dp[]\)会影响下标较大的,所以倒叙枚举,防止同一层枚举影响同一层较大下标的答案
就做出来了
要注意,因为\(dp\)中相当于存储的是指数,所以需要让\(dp\)对\(mod-1\)取模
爱看不看,不看滚
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int maxn=210;
int qpow(int a,int b){
int ans=1;
for(;b;b>>=1,a=a*a%mod){
if(b&1){
ans=ans*a%mod;
}
}
return ans;
}
int num[maxn*maxn];
int n;
signed main(){
cin>>n;
num[0]=1;
for(int i=1;i<=n;i++){
for(int j=i*(i+1)/2;j>=i;j--){
num[j]=(num[j]+num[j-i])%(mod-1);
}
}
int ans=1;
for(int i=1;i<=n*(n+1)/2;i++){
//cout<<num[i]<<" ";
ans=ans*qpow(i,num[i])%mod;
}
cout<<ans;
}
NOIP A层联测8(赛后版本)
T1 集合
爱看不看,不看滚
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int maxn=210;
int qpow(int a,int b){
int ans=1;
for(;b;b>>=1,a=a*a%mod){
if(b&1){
ans=ans*a%mod;
}
}
return ans;
}
int num[maxn*maxn];
int n;
signed main(){
cin>>n;
num[0]=1;
for(int i=1;i<=n;i++){
for(int j=i*(i+1)/2;j>=i;j--){
num[j]=(num[j]+num[j-i])%(mod-1);
}
}
int ans=1;
for(int i=1;i<=n*(n+1)/2;i++){
//cout<<num[i]<<" ";
ans=ans*qpow(i,num[i])%mod;
}
cout<<ans;
}
2023.10.10
CSP模拟51(赛时版本)
T1 菜
其实挺好做的,用一个栈来存入元素每次看是否可以合并,能合并就直接合,不能就直接存进栈
最后栈内元素只有1时输出"Yes"
第一班打了一个vector遍历的版本,先不说时间复杂度的问题,因为一些未知原因它炸了
爱看不看,不看滚
#include<bits/stdc++.h>
using namespace std;
bool st[710][710];
vector<int> num[710];
stack<int> s1;
stack<int> s2;
int n;
int main(){
freopen("2.in","r",stdin);
cin>>n;
for(int i=2;i<=700;i++){
for(int j=1;j<=sqrt(i);j++){
if(i%j==0){
if(j!=1)
num[i].push_back(j);
if(i!=j)
num[i].push_back(i/j);
}
}
}
int tot=0;
int turn;
for(int i=1;i<=n;i++){
int x;
cin>>x;
if(s1.empty()){
tot++;
s1.push(tot);
for(auto f:num[x]){
st[tot][f]=true;
}
}
else{
turn=s1.top();
bool flag=false;
for(auto f:num[x]){
//cout<<f<<" ";
if(st[turn][f]==true){
for(auto g:num[x]){
st[turn][g]=true;
}
flag=true;
break;
}
}
//cout<<endl;
if(!flag){
tot++;
s1.push(tot);
for(auto f:num[x]){
st[tot][f]=true;
}
//cout<<i<<" "<<tot<<endl;
}
}
cout<<i<<" "<<x<<endl;
}
if(s1.size()==1){
cout<<"Yes";
return 0;
}
bool tag=false;
int last=s1.size();
while(1){
if(tag==false){
turn=s1.top();
if(s2.empty()){
s2.push(turn);
}
else{
bool flag=false;
for(int i=2;i<=n;i++){
if(st[s2.top()][i]&&st[turn][i]){
for(int j=2;j<=n;j++){
if(st[turn][i]){
st[s2.top()][i]=true;
}
}
flag=true;
break;
}
}
if(!flag){
s2.push(turn);
}
}
s1.pop();
if(s1.empty()){
tag=true;
if(s2.size()==last){
cout<<"No";
return 0;
}
if(s2.size()==1){
cout<<"Yes";
return 0;
}
last=s2.size();
}
}
else{
turn=s2.top();
if(s1.empty()){
s1.push(turn);
}
else{
bool flag=false;
for(int i=2;i<=n;i++){
if(st[s1.top()][i]&&st[turn][i]){
for(int j=2;j<=n;j++){
if(st[turn][i]){
st[s1.top()][i]=true;
}
}
flag=true;
break;
}
}
if(!flag){
s1.push(turn);
}
}
s2.pop();
if(s2.empty()){
tag=false;
if(s1.size()==last){
cout<<"No";
return 0;
}
if(s1.size()==1){
cout<<"Yes";
return 0;
}
last=s1.size();
}
}
}
}
所以改了一个bitset版本,空间更小,速度更快
爱看不看,不看滚
#include<bits/stdc++.h>
using namespace std;
bitset <710>bit[710];
bitset <710>st[100100];
bitset <710>sta[100100];
bitset <710>site;
int n;
int main(){
//freopen("2.in","r",stdin);
cin>>n;
for(int i=2;i<=700;i++){
for(int j=1;j<=sqrt(i);j++){
if(i%j==0){
if(j!=1)
bit[i][j]=1;
if(i!=j)
bit[i][i/j]=1;
}
}
}
int top1=0,top2=0;
for(int i=1;i<=n;i++){
int x;
cin>>x;
if(top1==0){
top1++;
st[top1]=bit[x];
}
else{
site=(st[top1]&bit[x]);
if(site.any()){
st[top1]=(st[top1]|bit[x]);
}
else{
top1++;
st[top1]=(bit[x]);
}
}
//cout<<top1;
}
if(top1==1){
cout<<"Yes";
return 0;
}
int turn;
bool tag=false;
int last=top1;
while(1){
//cout<<top1<<" "<<top2<<endl;
if(tag==false){
turn=top1;
if(top2==0){
top2++;
sta[top2]=st[turn];
}
else{
site=(sta[top2]&st[turn]);
if(site.any()!=0){
sta[top2]=(sta[top2]|st[turn]);
}
else{
top2++;
sta[top2]=st[turn];
}
}
top1--;
if(!top1){
tag=true;
if(top2==last){
cout<<"No";
return 0;
}
if(top2==1){
cout<<"Yes";
return 0;
}
last=top2;
}
}
else{
turn=top2;
if(top1==0){
top1++;
st[top1]=sta[turn];
}
else{
site=(st[top1]&sta[turn]);
if(site.any()!=0){
st[top1]=(st[top1]|sta[turn]);
}
else{
top1++;
st[top1]=sta[turn];
}
}
top2--;
if(!top2){
tag=false;
if(top1==last){
cout<<"No";
return 0;
}
if(top1==1){
cout<<"Yes";
return 0;
}
last=top1;
}
}
}
}
CSP模拟51(赛后版本)
T2 狗(这是真狗)
由于左右方向和上下方向互不影响,所以我们分开计算考虑
由于每一行每一列是独立的,也单独计算
so,我们对每一行和每一列单独进行dp,得到状态转移方程
设\(i\)表示当前狗的数量,\(j\)表示有多少条标记为\(R\)或\(D\)的狗还没有匹配
\(f[i][j]\)统计方案数,\(g[i][j]\)统计权值和
对于当前标记为\(L\)或\(U\)的狗,转移方程如下
对于当前标记为\(R\)或\(D\)的狗,转移方程如下
最后,将每行每列权值与除了自己这行这列所得的方案数相乘,最后相加,即为答案
模数前一定要加const!!!妈的我调了一晚上
2023.10.11
CSP模拟52(赛时版本)
T1 长春花
打了个暴力,每次暴搜查找
对,切了
爱看不看,不看滚
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
bool flag[N];
int maxn=0;
signed main(){
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
int p;
cin>>p;
for(int i=0;i<p;i++){
flag[i*i%p]=true;
}
for(int i=0;i<p;i++){
for(int j=0;j<p;j++){
if(flag[(i-j*j+p)%p]){
maxn=max(maxn,j);
break;
}
}
}
cout<<maxn;
}
T2 紫罗兰
对于每一个点作为起点BFS,找到最小环长
当我们第一次访问\(v\)点时,更新其\(dis\),否则得到一个\(dis[u]+dis[v]+1\)的环
之后就记录方案数就可以了
因为每一个点都会把这个环统计一次,所以答案要除以环长
因为奇数环两个方向都会统计,所以奇数环要再把答案除以2
爱看不看,不看滚
#include<bits/stdc++.h>
#define int long long
using namespace std;
vector<int>edge[3010];
int n,m;
int minn=2e9+10;
int tot=0;
int dis[3010],len[3010];
void search(int x){
queue<int>q;
memset(dis,0x3f,sizeof(dis));
memset(len,0,sizeof(len));
q.push(x);
dis[x]=0,len[x]=1;
while(!q.empty()){
int i=q.front();
q.pop();
for(auto j:edge[i]){
int turnd=dis[i]+dis[j]+1;
int turnc=len[i]*len[j];
if(dis[j]==dis[i]||dis[j]==(dis[i]+1)){
if(turnd<minn){
minn=turnd;
tot=turnc;
}
else if(turnd==minn){
tot+=turnc;
}
}
if(dis[j]>dis[i]+1){
dis[j]=dis[i]+1;
len[j]=len[i];
q.push(j);
}
else if(dis[j]==dis[i]+1){
len[j]+=len[i];
}
}
}
}
signed main(){
freopen("B.in","r",stdin);
freopen("B.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
edge[x].push_back(y);
edge[y].push_back(x);
}
for(int i=1;i<=n;i++){
search(i);
}
tot/=minn;
if(minn%2)tot/=2;
cout<<tot;
}
T3 天竺葵
首先根据题意可以得到一些信息
首先,你选择的数之间的限制条件只于其在新序列中的位置和自身大小有关,也就是说在新序列的同一位置的数,倍数限制是一样大的
然后就是因为数据范围原因,新序列的数一定是单调不下降的
那就类比一下求单调不下降子序列的思想,首先就是\(O(n^2)\)的dp(这肯定不是正解了)
那就只有另外一种求最长不下降子序列的的算法了,线段树维护最长不下降子序列,时间复杂度\(O(nlogn)\)
看数据范围和时间限制,应该就是这个了
但是我没学线段树维护
大致看一下子任务,在没有推出正解是咋用线段树维护但是会dp的情况下,且知道咋用线段树求最长不下降子序列,可以拿到65pts
只会dp也有50pts
开始推dp
好了放弃了,推不出来
T4 风信子
能拿的分不多,运气好能有50pts
考虑分开打
首先第一档第二档就直接暴力查得了
k=1那一档可以\(O(n)\)遍历查询,但是它查询区间一旦大这就不行了
前缀最大值加后缀最大值维护?
好吧看来打朴素暴力吧
CSP模拟53(赛后版本)
T3 天竺葵
2023.10.12
NOIP A层联测10
T1 商品打包
手模得出来一个性质
对于一个物品序列,正序放入包内和倒序放入包内,所需要的包的数量是一定的
也就是说,我们把所有物品倒叙一个一个放进去,求得所需的包的数量就是答案
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+10;
int n,l;
int arr[maxn],ans[maxn];
signed main(){
freopen("pack.in","r",stdin);
freopen("pack.out","w",stdout);
cin>>n>>l;
for(int i=1;i<=n;i++){
cin>>arr[i];
}
int tot=1,turn=0;
for(int i=n;i>=1;i--){
if(turn+arr[i]<=l){
turn+=arr[i];
ans[i]=tot;
}
else{
turn=arr[i];
tot++;
ans[i]=tot;
}
}
for(int i=1;i<=n;i++){
cout<<ans[i]<<" ";
}
return 0;
}
T2 集合
考虑直接爆搜
用一个map来记录当前这个数是否已经搜过了,每搜到一个新数,就将其打上标记,并压入队列里
然后就一个一个找就行了
时间复杂度为啥是对的,可以看UU的博客
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int num[20];
bool vis[1000001000];
queue <int>q;
map<int,int>mp;
int tot=1;
void bfs(){
q.push(n);
vis[n]=true;
while(!q.empty()){
int x=q.front();
for(int i=1;i<=m;i++){
if(!vis[x/num[i]]){
q.push(x/num[i]);
vis[x/num[i]]=true;
tot++;
}
}
q.pop();
}
}
void bfs2(){
q.push(n);
mp[n]=1;
while(!q.empty()){
int x=q.front();
for(int i=1;i<=m;i++){
if(!mp[x/num[i]]){
q.push(x/num[i]);
mp[x/num[i]]=1;
tot++;
}
}
q.pop();
}
}
signed main(){
freopen("set.in","r",stdin);
freopen("set.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>num[i];
}
if(n<=1000000000){
bfs();
cout<<tot;
}
else if(m==1){
if(num[1]==1){
cout<<1;
return 0;
}
while(n){
n/=num[1];
tot++;
}
cout<<tot;
}
else{
bfs2();
cout<<tot;
}
}
T3
2023.10.13
CSP模拟54(赛时版本)
T1 数数
妈的啥逆天玩意啊
在没有第二个限制的情况下,排列总数高达\(b^{\sum_{i=1}^{300}}\),算出来最大值他妈的有\(1e406350\)
这个题应该就是个纯数学题,算出基本的排列方案再加上一堆乱七八糟的容斥
妈的我不会容斥啊
T2 Palindrome
我不知道别人看到这个东西首先想到的是啥,我先想到的东西是冒泡排序
假了,冒泡排序的方法是最差情况下的交换次数
那你这个东西咋做嘛,我手模两个样例了都没看明白
T3 Permutation
又他妈是个在全排列里找满足条件的
前一个数是后一个数的整数倍(+0,+1,+2)
20223.20.14
CSP模拟55(NOIP A层联测11)(赛时版本)
T1 签
貌似发现了一些特殊的性质
因为能交换的三个数在交换以后顺序是正确顺序,所以对于这三个数的中间那个数来说,它的位置应该也会都不会变了
然后发现对于同一时期所有可交换的三元组来说,交换的先后顺序对排序是没有影响的
然后就是对于每一次交换,影响到接下来的三元组只可能是交换后的三元组紧挨着的前后两个数
so,可以考虑用队列将所有符合条件的三元组中间数存储起来,同时记录已经到达正确位置的数的个数,依次按规则交换,交换后把新的可交换三元组中间数存进去,当队列为空时,判断到达正确位置数的个数是否和数的总量相等,然后输出yes或no
这个方法空间很小,只需要一个和\(3e5\)的数组和一个队列,并且每次查询只需要初始化正确位置的数为0,队列每次操作最后一定是空的.so不必再清空
很好,大样例都过了
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
int t,n;
int arr[maxn];
int tot=0;
queue <int>q;
void turn(int x,int y){
int z=arr[x];
arr[x]=arr[y];
arr[y]=z;
}
int main(){
// freopen("1.in","r",stdin);
cin>>t;
while(t--){
cin>>n;
tot=0;
for(int i=1;i<=n;i++){
cin>>arr[i];
}
for(int i=1;i<=n;i++){
if(arr[i]==i){
tot++;
if(i!=1&&i!=n&&arr[i-1]>arr[i]&&arr[i]>arr[i+1]){
q.push(i);
}
}
}
while(!q.empty()){
int id=q.front();
//cout<<id<<" ";
q.pop();
turn(id-1,id+1);
if(arr[id-1]==id-1)
tot++;
if(arr[id+1]==id+1)
tot++;
if(id!=n-1&&arr[id+2]==id+2&&arr[id+1]>arr[id+2]&&arr[id+2]>arr[id+3]){
q.push(id+2);
}
if(id!=2&&arr[id-2]==id-2&&arr[id-3]>arr[id-2]&&arr[id-2]>arr[id-1]){
q.push(id-2);
}
}
if(tot==n){
cout<<"Yes"<<endl;
}
else{
cout<<"No"<<endl;
}
}
}
T3 制糊串
想到了一个错解(但是数据太水了所以考场上很多人切了,但是因为这个错解没办法过样例我就没交)
查询字符串得到分值的最大值和最小值,查询值在此区间就输出“Yes”
最小值只会出现在以下情况:b串都是A,b串都是B,b串和a串完全一样
最大值遵循以下原则:a串依次查询,出现的AB或者BA组合在b串取反,空出的连续位置最长不会超过2,枚举每种情况,取最优
CSP模拟56(NOIP A层联测12)(赛时版本)
T1 醉
其实转化一下题意就很清晰这道题在干啥
每次给出一个起点\(u\),查询是否存在一点\(v\)在以\(u\)为根节点的树中深度恰好为\(d\)
暴力做法
直接跑dfs,时间复杂度\(O(n^2)\)
胡思乱想
首先想到的一个东西是换根dp,因为这道题查询操作是要把根换了的
如果从换根的角度思考,那就应该是先挑一个根进行dfs,之后按照连接关系给查询重新排序,因为查询相互独立,所以调换顺序是没有问题的
之后所有的答案建立在第一遍dfs的结果上,通过老根和新根的位置关系来找到符合要求的点
但是很明显的一个问题就是,在换根操作中,如果我们想要尽快找到答案,那就得记录新根在原dfs中所属子树,然后需要实现的操作大体是这样的
先dfs,记录以每个点作为子树根节点的子树的最大深度,相对应的点,以及每个点的深度.
对于每次查询,先看与上一次查询根节点是否相同,相同可以直接继承答案,从答案处寻找,不同我们就要以新根作为分割点,考虑原dfs中子树内和子树外,将子树内最大值减去根节点原深度,子树外最大值加上根节点原深度,判断是否存在答案,如果存在直接开找
那这种做法的问题在哪呢,在于特殊构造一条链,如果你对一条链进行查询,那你查询的最坏时间复杂度是\(O(\frac{n}{2})\)
这是没有办法接受的,如果一个链图,每次查询一定换根,而且查询的节点正好为最长子链的\(\frac{1}{2}\),那在极限数据下,我们要查询的量级就是\(\frac{n^2}{4}\),妥妥TLE
那就得考虑优化查询操作了,找是否存在一个点很容易,找具体的点很难
再想想吧……
T2 与
暴力做法
\(O(n)\)遍历查询
首先一个基本事实,,如果有一个序列是满足要求的,那么整个序列的与一定大于0,而不存在满足要求的序列,整个序列的与一定为0,从起点开始到终点,一路与过去与成0直接跳,看到终点结果如何
胡思乱想
想了一个根号分治,块长1000理论时间复杂度能过
但是问题是合并没法合,与操作很有可能会有多种选择,有可能与了a不能到达终点,但是与b可以
2023.10.17
CSP模拟57(NOIP A层联测13)(赛时版本)
T1 异或帽子
有一种解多元一次方程组的感觉
我们知道,给出n个数,再给出任意n-1个数的和,我们就可以求得n个数的和,然后求出每个数来
异或是可以类比加法的,因为一个数异或自己后为零,所以我们把这n个数的异或和异或起来,就得到了n个数的异或和,然后用这个和依次和给出的异或和异或,就可以求出原数
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int arr[200010],n,tot;
int main(){
freopen("hat.in","r",stdin);
freopen("hat.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++){
cin>>arr[i];
if(i==1)
tot=arr[i];
else tot^=arr[i];
}
for(int i=1;i<=n;i++){
cout<<(tot^arr[i])<<" ";
}
}
T2 传话游戏
大力dp
首先用hash处理每个字符串,方便判重
(欸,是不是直接用string就不用hash了)
(md,真是)
咳咳,用hash快啊,不用一个一个字符对比
(你妈hash不也是一个一个字符算出来的吗)
额,不考虑那些,只要能判断是否相等就行
用\(dp[i]\)表示到第i个字符串我们有多少种排列
不难发现,当我们加入一个新的字符时,只有两种可能
一是直接加入,这时候\(dp[i]\)直接加上\(dp[i-1]\)的方案数
二是当第\(i\)位和第\(i-1\)位的字符串不相同时,我们可以把这两个字符串交换位置,\(dp[i]\)加上\(dp[i-2]\)
点击查看代码
//你的大样例最好有点强度'-'
#include<bits/stdc++.h>
using namespace std;
string s[100010];
const int base=131,mod=998244353,mod2=1e9+7;
long long n;
long long Hash[100010],dp[100010];//删掉hash可减小内存
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++){
cin>>s[i];
}
for(int i=1;i<=n;i++){
for(int j=0;j<s[i].size();j++){
Hash[i]*=base;
Hash[i]%=mod;
Hash[i]+=s[i][j];
Hash[i]%=mod;//删掉hash可减少取模
}
}//hash部分可以直接删掉,把dp部分的判断改为直接判断字符串是否相等
dp[0]=1,dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]+=dp[i-1];
if(Hash[i]!=Hash[i-1]){
dp[i]+=dp[i-2];
dp[i]%=mod2;
}
}
cout<<dp[n];
return 0;
}
T3 全球覆盖
T4 幂次序列
不会,糊了一个\(O(n^2)\)的暴力,按照部分分应该有50pts,但是它本机跑了5s,寄
点击查看代码
#include<bits/stdc++.h>
using namespace std;
map<int,int>mp;
int num=0,n,ans=0;
int arr[200010];
queue<int>q;
void add(int x){
if(!mp[x]){
num++;
q.push(x);
}
mp[x]++;
if(mp[x]==2){
mp[x]=0;
num--;
add(x+1);
}
}
int main(){
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
cin>>n;
for(int i=1;i<=n;i++){
cin>>arr[i];
}
for(int i=1;i<=n;i++){
while(!q.empty()){
int turn=q.front();
mp[turn]=0;
q.pop();
}
num=0;
for(int j=i;j<=n;j++){
add(arr[j]);
if(num==1)
ans++;
}
}
cout<<ans;
}
2023.10.19
CSP模拟59
T1 信号塔
通过读题我们发现,每次所选择的信号塔一定在某个区间的范围内最高,所以,在整个区间范围内最高的信号塔,是必须选择同时也是最后一个选择的
所以题意转化成了
2023.11.1
(咕了很久,重新开始更)
NOIP模拟8(赛时版本)
T1 集合
首先看数据范围就会觉得很奇怪
2e5的数据,一开始怀疑是不是个\(O(nlogn^2)\)的时间复杂度,刚好跑满
但是想了想又感觉不大对劲,没有啥算法可以组合出这个复杂度来
开始找性质
首先,子集的数量是一定的,最大不超过2e10
然后,我们开始倒着kao