上海理工大学天梯赛2022补题
目录
二进制
不难发现,K数列本质就是每一个K进制位只能取0或1,这很像二进制,即第n个数就是
把n表示成二进制然后按K进制还原即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL maxn = 1e7+10,mod=1e9+7;
int a[maxn];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
LL n, k; cin>>n>>k;
LL w = n, ans = 0, t = 1;
while(w){
if(w&1){
ans = (ans+t)%mod;
}
t = t*k%mod;
w >>= 1;
}
cout<<ans<<"\n";
return 0;
}
十进制转二进制的巧妙方法
DFS
思路简单,但是对中国象棋特判的时候,直走一步进行判断的时候,不仅要判断这个位置有没有棋子,还要判断这个位置有没有出界,这一点容易忽略
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 310;
int T, n, m, k, a, b, c, d;
bool map[N][N], vis[N][N];
int dx[8] = {1, 1, -1, -1, 2, 2, -2, -2};
int dy[8] = {2, -2, 2, -2, 1, -1, 1, -1};
int ddx[8] = {0, 0, 0, 0, 1, 1, -1, -1};
int ddy[8] = {1, -1, 1, -1, 0, 0, 0, 0,};
int dist[N][N];
bool check(int x, int y)
{
if(x <= 0 || y <= 0 || x > n || y > m) return false;
return true;
}
int bfs2(int x, int y)//American
{
queue<PII> q;
memset(dist, 0, sizeof dist);
memset(vis, 0, sizeof vis);
q.push({x, y});
dist[x][y] = 0;
vis[x][y] = true;
while(q.size())
{
auto t = q.front();
q.pop();
int distance = dist[t.x][t.y];
if(t.x == c && t.y == d) return distance;
for(int i = 0; i < 8; i ++ )
{
int xx = t.x + dx[i], yy = t.y + dy[i];
if(check(xx, yy) && !vis[xx][yy] && !map[xx][yy])
{
dist[xx][yy] = distance + 1;
q.push({xx, yy});
vis[xx][yy] = true;
}
}
}
return -1;
}
int bfs1(int x, int y)//china
{
queue<PII> q;
memset(dist, 0, sizeof dist);
memset(vis, 0, sizeof vis);
q.push({x, y});
dist[x][y] = 0;
vis[x][y] = true;
while(q.size())
{
auto t = q.front();
q.pop();
int distance = dist[t.x][t.y];
if(t.x == c && t.y == d) return distance;
for(int i = 0; i < 8; i ++ )
{
int xx = t.x + dx[i], yy = t.y + dy[i];
int nx = t.x + ddx[i], ny= t.y + ddy[i];
//难点主要在于这里的边界判断
//第一行的边界判断好理解
//第二行是说,有可能走一步也会产生边界问题
if(check(xx, yy) && !map[xx][yy] && !vis[xx][yy] &&
check(nx, ny) && !map[nx][ny])
{
dist[xx][yy] = distance + 1;
q.push({xx, yy});
vis[xx][yy] = true;
}
}
}
return -1;
}
int main()
{
cin >> T ;
while(T -- )
{
memset(map, 0, sizeof map);
cin >> n >> m >> k >> a >> b >> c >> d;
for(int i = 0; i < k; i ++ )
{
int x, y;
cin >> x >> y;
map[x][y] = true;
}
cout << bfs2(a, b) << " ";
cout << bfs1(a, b) << endl;
}
return 0;
}
DP + 保存路径
• 如果不用按字典序输出,那么就是一个很经典的线性 dp 问题,转移方程:dp[i][j] = min(dp[i-1][j], dp[i-1][j-h[i]] + 1)• 其中 dp i,j 表示前 i 个数最少取几个数可以使它们的和正好等于 j 。但现在还需要按照字典序输出,那么可以将所有 h i 降序排列,在状态转移时优先考虑加入当前的 h i ,即优先转移 dp[ i-1][j-h[i] ]+1 ,并记录当前序列的最小数,以方便后续输出。
#include <iostream>
#include <cstring>
#include <algorithm>
#define debug1(val) cout << #val << " = " << val
using namespace std;
const int N = 3010, INF = 0x3f3f3f3f;
int n, H, res;
int h[N], dp[N];
int main()
{
cin >> n >> H;
for(int i = 1; i <= n; i ++ ) cin >> h[i];
sort(h + 1, h + 1 + n);
memset(dp, 0x3f, sizeof dp);
dp[0] = 0;
for(int i = 1; i <= n; i ++ )
for(int j = H; j >= h[i]; j -- )//降序排列,优先加入当前i
dp[j] = min(dp[j], dp[j - h[i]] + 1);
if(dp[H] == INF) cout << -1 << endl;
else
{
cout << dp[H] << endl;
int res = dp[H];
for(int i = 1; i <= n; i ++ )
{
if(dp[H - h[i]] == res - 1)
{
cout << h[i] << ' ';
H -= h[i];
res -- ;
if(!res) break;
}
}
}
return 0;
}
Trie树
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010, M = 2000010;
int n, a[N], cnt[N];
int tr[M][20], idx;
void Insert(int x)
{
string str = to_string(x);
while(str.size() < 6) str = "0" + str;
int p = 0;
for(int i = 0; str[i]; i ++ )
{
int u = str[i] - '0';
if(!tr[p][u]) tr[p][u] = ++ idx;
p = tr[p][u];
cnt[p] ++ ;//??
}
}
int query(int x)
{
string str = to_string(x);
while(str.size() < 6) str = "0" + str;
int p = 0, ans = 0;
for(int i = 0; str[i]; i ++ )
{
int u = 9 - (str[i] - '0');
while(1)
{
if(cnt[tr[p][u]])
{
p = tr[p][u];
ans = ans * 10 + (u + str[i] - '0') % 10;
break;
}
u -- ;
if(u < 0) u = 9;
}
}
return ans;
}
void erase(int x)
{
string str = to_string(x);
while(str.size() < 6) str = '0' + str;
int p = 0;
for(int i = 0; str[i]; i ++ )
{
int u = str[i] - '0';
p = tr[p][u];
cnt[p] -- ;
}
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
for(int i = 0; i < n; i ++ )
{
cin >> a[i];
Insert(a[i]);
}
for(int i = 0; i < n; i ++ )
{
erase(a[i]);
int res = query(a[i]);
cout << res << " ";
Insert(a[i]);
}
cout << endl;
return 0;
}
并查集
• 由于题目里提示了“不会重复剪同样的位置”,可以考虑将绳子上的 1000001 个(包括左右两端点)都单独视为一个集合,并将询问记录下来,倒序考虑,一开始根据绳子上所有被剪过的端点将绳子分成若干部分,并用并查集将这些部分各自合并起来,然后根据询问的倒序依次处理,若是 "A f" ,则输出 f 所在集合的长度,若是 "C f" ,则合并 f 与 f-1所在的集合
#include<bits/stdc++.h>
using namespace std;
vector<double>ve;
int n;
int main(void)
{
cin>>n;
ve.push_back(0),ve.push_back(10);
for(int i=0;i<n;i++)
{
string op; cin>>op;
if(op=="C")
{
double x; cin>>x;
ve.push_back(x);
sort(ve.begin(),ve.end());
}else
{
double x; cin>>x;
if(x==10) printf("%.5lf\n",10-ve[ve.size()-2]);//最后一个位置
else
{
int index=upper_bound(ve.begin(),ve.end(),x)-ve.begin();
printf("%.5lf\n",ve[index]-ve[index-1]);
}
}
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
int main(){
int t;
cin>>t;
set<int> st;
st.insert(0);
st.insert(1000000);
while(t--){
char s[2];
int x,y;
scanf("%s %d.%d",s,&x,&y);
int cur=x*100000+y;
if(s[0]=='C'){
st.insert(cur);
}else{
if(cur==1000000){
int res=1000000-*prev(st.lower_bound(cur));
printf("%d.%05d\n",res/100000,res%100000);
}else{
int res=*st.upper_bound(cur)-*prev(st.upper_bound(cur));
printf("%d.%05d\n",res/100000,res%100000);
}
}
}
}
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
cin >> t;
set<double> s;
s.insert(0.0);
s.insert(10.0);
while(t--)
{
char c;
double x;
cin >> c >> x;
if(c=='C')
{
s.insert(x);
}
else if(c=='A')
{
auto it = s.lower_bound(x);
if(x<=1e-6)
printf("%.5f\n", *s.upper_bound(x));
else
printf("%.5f\n", *it - *prev(it));
}
}
return 0;
}