A.折纸
题目描述
小s很喜欢折纸。
有一天,他得到了一条很长的纸带,他把它从左向右均匀划分为(n)个单位长度,并且在每份的边界处分别标上数字(1-n)。
然后小s开始无聊的折纸,每次他都会选择一个数字,把纸带沿这个数字当前所在的位置翻折(假如已经在边界上了那就相当于什么都不做)。
小s想知道 次翻折之后纸带还有多长。
输入格式
输入包含多组数据,第一为一个正整数(T)。
接下来,每组数据包括两行。
第一行包含两个正整数(n)和(m),表示纸带的长度和操作的次数。
第二行包含(m)个整数(Di),其中(Di)表示第(i)次选择的数字。
输出格式
每组数据输出一行,只有一个数字,即纸带最后的长度。
样例
(input)
2
5 2
3 5
5 2
3 2
(output)
2
2
思路
思路1(并查集模拟)
把折叠后重合的点连起来,最后看有多少个集合并减1(因为是区间),得到答案。
#include <cstdio>
#include <cstring>
#include <iostream> //long long
#include <algorithm>
using namespace std;
const int maxn = 3e3 + 5, INF = 0x3f3f3f3f;
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
int n, m, f[10000005];
void Init() {
for (int i = 1; i <= n; i++) f[i] = i;
}
int Find(int x) {
if (f[x] == x)
return x;
return f[x] = Find(f[x]);
}
void Merge(int a, int b) { f[Find(b)] = Find(a); }
int main() {
int T = read();
while (T--) {
n = read(), m = read();
Init();
int l = 0, r = n;
for (int i = 1, now; i <= m; i++) {
now = read();
now = Find(now);
if (now > r)
now = r * 2 - now;
else if (now < l)
now = l * 2 - now;
if (now - l > r - now) {
for (int j = now + 1; j <= r; j++) Merge(now * 2 - j, j);
r = now;
} else {
for (int j = l; j <= now - 1; j++) Merge(now * 2 - j, j);
l = now;
}
// cout<<l<<" "<<r<<" "<<endl;
}
cout << r - l << endl;
}
return 0;
}
思路2(正解,维护左右端点)
由于每次折叠都是由前一个折叠后的状态转移过来的,所以我们将每次折叠实际位置维护起来,不断更新,要是统一都向右折,根据数据范围显然会炸long long,必须要开unsigned long long,可以都向中间折叠,可以不用开unsigned long long,下代码并没有向中间折叠,实际上方法是一样的。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define rint register int
using namespace std;
const int maxn = 3050;
inline unsigned long long read(){
unsigned long long x = 0, f = 1; char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int t, m;
unsigned long long n, sol[maxn];
int main(){
t = read();
while(t--){
n = read(), m = read();
unsigned long long l = 0, r = n;
for(rint i = 1; i <= m; i++){
sol[i] = read();
for(rint j = 1; j < i; j++){
if(sol[j] <= sol[i]) continue;
else{
sol[i] = sol[j] * 2 - sol[i];
}
}
r = max(sol[i] * 2 - l, r);
l = sol[i];
}
printf("%lld
", r - l);
}
return 0;
}
B.water(来源POI1999,略有不同)
题目描述
有一块矩形土地被划分成(n imes m)个正方形小块。这些小块高低不平,每一小块都有自己的高度。水流可以由任意一块地流向周围四个方向的四块地中,但是不能直接流入对角相连的小块中。
一场大雨后,由于地势高低不同,许多地方都积存了不少降水。给定每个小块的高度,求每个小块的积水高度。
注意:假设矩形地外围无限大且高度为0。
输入格式
第一行包含两个非负整数n,m。
接下来n行每行m个整数表示第i行第j列的小块的高度。
输出格式
输出n行,每行m个由空格隔开的非负整数,表示每个小块的积水高度。
样例
(input)
3 3
4 4 0
2 1 3
3 3 -1
(output)
0 0 0
0 1 0
0 0 1
思路
本题目有两种解法,一种是用朴素的宽搜解决问题(考试的时候时间不够懒得写了),另一种是gyz大佬想出来的kruskal的方法(挺难想到的),orz。
思路1
外围高度为0,所以高度为负的至少能够装水到地平,首先能确定储水量的是矩形的四边上的点,对于其他的点就不好直接判断了,但根据水桶原理,能储多少水取决于最短的那块木板,所以我们可以把四边上的每一个点放到一个小根堆里,依次从高度最低的点往里搜,显然比他高的点是存不了水的,比他低的点可以存储他们高度差的水量,依次搜索即可。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 300 + 5, INF = 0x3f3f3f3f, kx[4] = { 0, 1, 0, -1 }, ky[4] = { 1, 0, -1, 0 };
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
struct Node {
int x, y, w;
friend bool operator<(const Node &A, const Node &B) { return A.w > B.w; }
};
int n, m, a[maxn][maxn], h[maxn][maxn], black[maxn][maxn];
priority_queue<Node> q;
int main() {
n = read(), m = read();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) h[i][j] = a[i][j] = read();
for (int i = 0; i <= n + 1; i++) q.push((Node){ 0, i, 0 });
for (int i = 0; i <= m + 1; i++) q.push((Node){ i, 0, 0 });
while (!q.empty()) {
Node now = q.top();
q.pop();
int x = now.x, y = now.y, w = now.w;
for (int i = 0; i < 4; i++) {
int nx = x + kx[i], ny = y + ky[i];
if (nx < 0 || ny < 0 || nx > n + 1 || ny > m + 1 || black[nx][ny])
continue;
h[nx][ny] = max(h[nx][ny], w);
black[nx][ny] = 1;
q.push((Node){ nx, ny, h[nx][ny] });
}
}
for (int i = 1; i <= n; i++, puts(" "))
for (int j = 1; j <= m; j++) cout << h[i][j] - a[i][j] << " ";
return 0;
}
思路2
(前方高能,非战斗人员请迅速撤离)
建边用两点中高度最大的点的高度为边权,考虑一个问题,一条路径中最大的边权就是这个坑的最大深度,因为要是有更大的路径都没有选择。本蒟蒻目前还没有完全理解,等悟了之后一定重新缕一边思路,目前只悟了这么点。
#include<bits/stdc++.h>
using namespace std;
const int M=310*310,N=310;
struct Edge{
int fr,to,nxt,val;
bool operator < (const Edge&A)const{
return val<A.val;
}
}e[M],t[M<<2];
struct Node{
int x,y;
Node(){}
Node(int a,int b){
x=a;y=b;
}
bool operator < (const Node&A)const{
return x<A.x;
}
};
map<int,Node> col;
int h[M],idx;
void Ins(int a,int b,int c){
e[++idx].to=b;e[idx].nxt=h[a];
h[a]=idx;e[idx].val=c;
}
int n,m;
int Mat[N][N],mp[N][N],ans[N][N],cnt;
int f[M];
int find(int x){
return x==f[x]?x:(f[x]=find(f[x]));
}
void dfs(int u,int fa,int Mx){
Node now=col[u];
if(Mat[now.x][now.y]<=Mx)ans[now.x][now.y]=Mx-Mat[now.x][now.y];
else ans[now.x][now.y]=0;
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa)continue;
dfs(v,u,max(Mx,e[i].val));
}
}
int main(){
scanf("%d%d",&n,&m);
col[0]=Node(0,0);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&Mat[i][j]);
mp[i][j]=++cnt;
col[cnt]=Node(i,j);
}
}
for(int i=1;i<=cnt;i++)f[i]=i;
cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
t[++cnt].fr=mp[i][j];
t[cnt].to=mp[i][j+1];
t[cnt].val=max(Mat[i][j],Mat[i][j+1]);
t[++cnt].fr=mp[i][j];
t[cnt].to=mp[i+1][j];
t[cnt].val=max(Mat[i][j],Mat[i+1][j]);
t[++cnt].fr=mp[i][j];
t[cnt].to=mp[i][j-1];
t[cnt].val=max(Mat[i][j],Mat[i][j-1]);
t[++cnt].fr=mp[i][j];
t[cnt].to=mp[i-1][j];
t[cnt].val=max(Mat[i][j],Mat[i-1][j]);
}
}
sort(t+1,t+cnt+1);
for(int i=1;i<=cnt;i++){
int u=find(t[i].fr),v=find(t[i].to);
if(u!=v){
f[u]=v;
Ins(u,v,t[i].val);Ins(v,u,t[i].val);
}
}
dfs(0,-1,-0x7fffffff);
for(int i=1;i<=n;i++,puts(""))
for(int j=1;j<=m;j++)
printf("%d ",ans[i][j]);
}
找伙伴
题目描述
在班级里,每个人都想找学习伙伴。伙伴不能随便找,都是老师固定好的,老师给出要求:伙伴要凭自己实力去找。
老师给每个人发一张纸,上面有数字。假设你的数字是w,那么如果某位同学手中的数字的所有正约数之和等于w,那么这位同学就是你的小伙伴。
输入格式
输入包含n组数据(最多100组)
对于每组测试数据,输入只有一个数字w。
输出格式
对于每组数据输出两行,第一行包含一个整数m,表示有m(如果m = 0,只输出一行0即可)个伙伴。
第二行包含相应的m个数,表示伙伴的数字。注意:小伙伴的数字必须按照升序排列。
样例
(input)
(output)
思路
简单的数论
暴力算法(显然会TLE)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
#define rint register int
using namespace std;
inline int read(){
int x = 0, f = 1; char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
const int maxn = 1e8 + 50;
int a[maxn], cnt, col[maxn];
bool flag;
inline void solve(int x,int k){
col[x] = 0;
int sol = sqrt(x);
for(rint i = 1; i <= sol; i++)
if(x % i == 0){ col[x] += i;col[x] += x/i;}
if(sol * sol == x){ col[x] -= sol;}
if(col[x] == k){ flag = true;a[++cnt] = x;}
col[x] = col[x];
return;
}
int main(){
//freopen("a.in", "r", stdin);
int n;
while(~scanf("%d", &n)){
cnt = 0, flag = 0;
for(rint i = 1; i <= n; ++i){
if(col[i] != n && col[i]) continue;
else solve(i, n);
}
if(flag == false){
printf("0
");
continue;
}
printf("%d
", cnt);
for(rint i = 1; i < cnt; ++i){
printf("%d ", a[i]);
}
printf("%d
", a[cnt]);
}
return 0;
}
正解算法
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e4;
bool is_not_prime[maxn];
int prime[maxn];
int m;
void primes(int n) {
is_not_prime[1] = 1;
for (int i = 2; i <= n; i++) {
if (!is_not_prime[i])
prime[++prime[0]] = i;
for (int j = 1; i * prime[j] <= n; j++) {
is_not_prime[i * prime[j]] = 1;
if (i % prime[j] == 0)
break;
}
}
}
bool is_prime(int x) {
if (x <= maxn)
return !is_not_prime[x];
for (int i = 1; prime[i] * prime[i] <= x; i++) {
if (x % prime[i] == 0)
return 0;
}
return 1;
}
int a[maxn], cnt;
void dfs(int now, int p, int x) {
if (now == 1) {
a[++cnt] = x;
return;
}
if (is_prime(now - 1) && now > prime[p])
a[++cnt] = x * (now - 1);
for (int i = p; prime[i] * prime[i] <= now; i++) {
int pi = prime[i];
int sum = prime[i] + 1;
for (; sum <= now; pi *= prime[i], sum += pi) {
if (now % sum == 0)
dfs(now / sum, i + 1, x * pi);
}
}
}
int main() {
primes(maxn);
while (scanf("%d", &m) != EOF) {
cnt = 0;
dfs(m, 1, 1);
printf("%d
", cnt);
sort(a + 1, a + cnt + 1);
for (int i = 1; i <= cnt; i++) {
printf("%d ", a[i]);
}
printf("
");
}
}
D.string
题目描述
给定一个由小写字母组成的字符串 s。有 m 次操作,每次操作给定 3 个参数 l,r,x。如果 x=1,将 s[l]∼s[r] 升序排序;如果 x=0,将 s[l] s[r] 降序排序。你需要求出最终序列。
输入格式
第一行两个整数 n,m,表示字符串长度为n,有m次操作。
第二行一个字符串s。
接下来m行每行三个整数 l,r,x。
输出格式
一行一个字符串表示答案。
样例
(input)
5 2
cabcd
1 3 1
3 5 0
(output)
abdcc
思路
根据v数据范围可以看出来单纯的sort肯定解决不了问题,再想一下,英文字母只有26个,那么这么大的序列里面肯定会有很多一样的字符,看数据范围和操作的大概样式,可以联想到线段树,要是sort的话可以把很多个字符一起sort,可以大大提高效率。
代码实现
#include <cstdio>
#include <cstring>
#define lson (p * 2)
#define rson (p * 2 + 1)
#define mid ((l + r >> 1))
using namespace std;
char s[100005];
int n, m, l, r, x, tree[400005], cnt[30];
void build(int p, int l, int r) {
if (l == r) return tree[p] = s[l] - 'a' + 1, void();
build(lson, l, mid), build(rson, mid + 1, r);
tree[p] = (tree[lson] == tree[rson]) ? tree[lson] : 0;
}
void query(int p, int l, int r, int s, int t) {
if (s <= l && r <= t && tree[p]) return cnt[tree[p]] += r - l + 1, void();
if (tree[p] && l != r) tree[lson] = tree[p], tree[rson] = tree[p];
if (s <= mid) query(lson, l, mid, s, t);
if (t > mid) query(rson, mid + 1, r, s, t);
}
void update(int p, int l, int r, int s, int t, int v) {
if (s <= l && r <= t || tree[p] == v) return tree[p] = v, void();
if (tree[p] && l != r) tree[lson] = tree[p], tree[rson] = tree[p];
if (s <= mid) update(lson, l, mid, s, t, v);
if (t > mid) update(rson, mid + 1, r, s, t, v);
tree[p] = (tree[lson] == tree[rson]) ? tree[lson] : 0;
}
void print(int p, int l, int r) {
if (l == r || tree[p]) for (int i = l; i <= r; i++) putchar(tree[p] + 'a' - 1);
else print(lson, l, mid), print(rson, mid + 1, r);
}
int main() {
scanf("%d%d%s", &n, &m, s + 1);
build(1, 1, n);
for (int i = 1; i <= m; i++) {
scanf("%d%d%d", &l, &r, &x);
memset(cnt, 0, sizeof(cnt));
query(1, 1, n, l, r);
if (x) {
for (int i = 1; i <= 26; i++)
if (cnt[i]) update(1, 1, n, l, l + cnt[i] - 1, i), l = l + cnt[i];
} else {
for (int i = 26; i >= 1; i--)
if (cnt[i]) update(1, 1, n, l, l + cnt[i] - 1, i), l = l + cnt[i];
}
}
print(1, 1, n);
return 0;
}