「TAOI-2」核心共振,「NnOI R2-T3」Horizon Blue
「TAOI-2」核心共振
# 「TAOI-2」核心共振
## 题目背景
⚡超越一切震慑凡人⚡
⚡带来终结机械降神⚡
⚡风暴之力充满全身⚡
⚡最后一击核心共振⚡
## 题目描述
给定正整数 $p$ 和 $n$。对于一个排列,我们称其中相邻两项产生「共振」当且仅当这两个数的和为 $p$ 的倍数。
请你构造一个 $1 \sim n$ 的排列,最大化其中产生「共振」的次数。如果有多种方案,输出任意一种即可。
## 输入格式
**本题有多组测试数据。**
输入的第一行包含一个正整数 $T$,代表数据的组数。
对于每组测试数据,输入包含一行,为由空格隔开的两个正整数 $n$ 和 $p$。
## 输出格式
对于每组测试数据,输出一行,包含由空格隔开的 $n$ 个正整数,代表你构造的排列。
## 样例 #1
### 样例输入 #1
```
3
9 1
5 2
1 12345
```
### 样例输出 #1
```
3 8 7 1 4 5 6 9 2
1 5 3 2 4
1
```
## 提示
**本题采用捆绑测试。**
+ Subtask 0(15 pts):$n \leq 9$,$T \le 10$。
+ Subtask 1(10 pts):$p = 2$。
+ Subtask 2(30 pts):$p = 3$。
+ Subtask 3(45 pts):无特殊限制。
对于所有数据,$1 \leq n \leq 10^5$,$1 \leq p \leq 10^8$,$1 \leq T \leq 10^4$,$1 \leq \sum n \leq 3\times 10^5$。
这道题目我在赛场上面没有想到,赛后查看博客之后才得到一丝启发
有这样一个规律,既然要求和为p,那么我们只需要让两个数对p取模之后,如果两个数的余数相加=p的话那就是p的倍数了
所以说先把p的倍数放在前面,然后将 余数和为p的两个数相间放就可以
先上一段部分TLE的代码:
#include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; string s; int n,t,a[N],f[N],res,num,ans,m,k; bool vis[N]; void solve() { cin>>n>>k; map<int,vector<int>>mp; for(int i=1;i<=n;i++) mp[i%k].push_back(i); for(auto i:mp[0]) cout<<i<<" "; for(int i=1;i<=k/2;i++){ int p=k-i,num=0; if(p==i) for(auto j:mp[p]) cout<<j<<" "; else{ for(auto j:mp[i]){ cout<<j<<" "; if(num+1<=mp[p].size()) cout<<mp[p][num++]<<" "; } } } cout<<endl; } signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>t; while(t--){ solve(); } return 0; }
为什么会超时呢?通过分析得知我们的复杂度只能是O(n*m/2),所以说可能出现一种m过于大的情况,而又没有n与之匹配
所以说我们在开头特判一下即可,如果m>=2*n的话,那就直接输出原序列就可以
#include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; string s; int n,t,a[N],f[N],res,num,ans,m,k; bool vis[N]; void solve() { cin>>n>>k; map<int,vector<int>>mp; if(k>=n*2){ for(int i=1;i<=n;i++) cout<<i<<" "; cout<<endl; return; } for(int i=1;i<=n;i++) mp[i%k].push_back(i); for(auto i:mp[0]) cout<<i<<" "; for(int i=1;i<=k/2;i++){ int p=k-i,num=0; if(p==i) for(auto j:mp[p]) cout<<j<<" "; else{ for(auto j:mp[i]){ cout<<j<<" "; if(num+1<=mp[p].size()) cout<<mp[p][num++]<<" "; } } } cout<<endl; } signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>t; while(t--){ solve(); } return 0; }
「NnOI R2-T3」Horizon Blue
# 「NnOI R2-T3」Horizon Blue
## 题目描述
小 C 喜欢在画板上画画。他进行了 $ n $ 次操作,每次操作有如下三种可能:
- ```1 k b``` 代表小 C 绘制了一条解析式为 $ y=kx+b $ 的直线。
- ```2 k b``` 代表小 C 询问你直线 $ y=kx+b $ 与多少条被绘制的直线有**恰好**一个公共点。
- ```3 k b``` 代表小 C 擦除所有与直线 $ y=kx+b $ 有**至少**一个公共点的直线。
**注意:两条重合的直线有无数个交点。**
**注意:询问时重合的直线应分别计算。**
## 输入格式
第一行一个整数 $ n $。
接下来 $n$ 行,每行三个整数 $ 1/2/3,k,b $,代表一次操作。
## 输出格式
对每次 ```2 k b``` 操作,输出满足要求的直线数量。
## 样例 #1
### 样例输入 #1
```
6
1 1 0
1 -1 0
2 2 1
3 1 3
2 2 1
2 1 1
```
### 样例输出 #1
```
2
1
0
```
## 样例 #2
### 样例输入 #2
```
10
1 1 0
1 1 0
2 1 1
2 1 0
2 2 5
3 1 0
2 2 5
1 2 3
1 3 4
2 3 5
```
### 样例输出 #2
```
0
0
2
0
1
```
## 提示
**【样例 1 解释】**
第 1 次操作,绘制直线 $ y=x $。
第 2 次操作,绘制直线 $ y=-x $。
第 3 次操作,可以发现直线 $ y=2x+1 $ 与前两条线相交。
第 4 次操作,擦掉所有 $ y=x+3 $ 相交的线,直线 $ y=-x $ 被擦掉。
第 5 次操作,$ y=2x+1 $ 显然与 $ y=x $ 相交。
第 6 次操作,$ y=x+1 $ 与 $ y=x $ 斜率相等,是平行线,不相交。
**【数据范围】**
对于 $ 100\% $ 的数据,$ 1 \le n \le 10^5 $,$ 1 \le |k| \le 10^5 $,$ 0 \le |b| \le 10^5 $。
**提示:本题开启捆绑测试。**
$$
\def\r{\cr\hline}
\def\None{\text{None}}
\def\arraystretch{1.5}
\begin{array}{c|c|c}
\textbf{Subtask} & \textbf{Sp. Constraints} & \textbf{Score}\r
\textsf1& n \le 5000 & 27 \r
\textsf2& \vert k\vert,\vert b\vert \le 50 & 21 \r
\textsf3& 无第\ 3\ 类操作 & 13 \r
\textsf4& 第\ i\ 次操作满足\ k=i & 14 \r
\textsf5& 无特殊限制 & 25 \r
\end{array}
$$
61分超时代码:
#include<bits/stdc++.h> using namespace std; const int N=2e5+10; int n,m,num; int vis[N],res[N],cnt; map<int,int>mp; map<pair<int,int>,int>mp_; int main() { cin>>n; for(int i=0;i<n;i++){ int a,k,b; scanf("%d%d%d",&a,&k,&b); if(a==1) num++,mp[k]++,mp_[{k,b}]++; if(a==2) printf("%d\n",num-mp[k]); if(a==3){ for(auto &i:mp) if(i.first!=k) num-=i.second,i.second=0; for(auto &i:mp_){ if(i.first==make_pair(k,b)) {if(i.second!=0) num-=i.second,mp[k]-=i.second,i.second=0;} else if(i.first.first!=k) i.second=0; } } } return 0; }
考虑优化,使用二维map,然后使用clear()清楚,时间复杂度O(nlogn)
原因:因为遍历的复杂度是O(n),n为map的元素个数,由于遍历之后我直接clear,使得每个线段只会被遍历一次
但是如果只是将值赋为0的话,先前的元素还在里面,只是数值为零,以后遍历还是会遍历到,导致总体复杂度最坏情况下达到 O(n^2)
#include<bits/stdc++.h> using namespace std; const int N=2e5+10; int n,m,num; int vis[N],res[N],cnt; map<int,int>mp; map<int,unordered_map<int,int>>mp_; int main() { cin>>n; for(int i=0;i<n;i++){ int a,k,b; scanf("%d%d%d",&a,&k,&b); if(a==1) num++,mp[k]++,mp_[k][b]++; if(a==2) printf("%d\n",num-mp[k]); if(a==3){ for(auto &i:mp) if(i.first!=k) num-=i.second,mp_.erase(i.first); mp.clear(); num-=mp_[k][b],mp_[k][b]=0,mp[k]=num; } } return 0; }
这是超时的,原因就是每次都单提一个map导致时间复杂度变高:
#include<bits/stdc++.h> using namespace std; const int N=2e5+10; int n,m,num; int vis[N],res[N],cnt; map<int,int>mp; map<int,unordered_map<int,int>>mp_; int main() { cin>>n; for(int i=0;i<n;i++){ int a,k,b; scanf("%d%d%d",&a,&k,&b); if(a==1) num++,mp[k]++,mp_[k][b]++; if(a==2) printf("%d\n",num-mp[k]); if(a==3){ for(auto &i:mp) if(i.first!=k) num-=i.second,i.second=0,mp_.erase(i.first); mp[k]-=mp_[k][b],num-=mp_[k][b],mp_[k][b]=0; } } return 0; }