做完这套题感觉如释重负。。只有一种感觉:好难,连前2道都让我感觉很困难。单看代码其实不怎么样,但它的思路确实让我感到了和大佬的差距。
A题题意:给你个数为n的序列,让你将该序列分成若干个小序列,小序列开头,结尾,个数都必须为奇数,并且,小序列的总个数也必须为奇数。问你是否有可能。
思路:博主的第一想法是模拟。。然而仔细想想该题有其奥妙在内。试想,小序列元素个数是奇数个,而小序列总个数也是奇数个,那么原本的序列总个数必然是奇数个,所以如果n为偶数,则必然输出NO(哭),然后如果n为奇数,只需判断第一个数和最后一个数是不是奇数,如果不是奇数,那么肯定拿不完,如果都是奇数,那么直接全部拿走,满足题意。
这题差点没做出来,唉。。
代码如下:
#include<iostream>
using namespace std;
#pragma warning (disable:4996)
int main()
{
int n;
scanf("%d", &n);
int x;
bool flag = true;
for (int i = 1;i <= n;i++)
{
scanf("%d", &x);
if (i == 1 || i == n)
{
if (x % 2 == 0)flag = false;
}
}
if (flag&&n % 2)
puts("YES");
else puts("NO");
return 0;
}
B题题意:给你n个点的纵坐标,他们的横坐标依次为1到n,问这n个点是否能组成两条平行的线。
思路:前三个点1,2,3必然有两个在一条直线上,所以分3类,然后删掉在该直线上的点,再判断其他的点是不是能组成一条与该直线平行的线。
#include<iostream>
#include<cstring>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 1005;
long long y[maxn];
bool vis[maxn];
int n;
void solve(long long k,int temp)
{
long long yy = y[temp];
vis[temp] = 1;
for (int i = 1;i <= n;i++)
{
if (1LL*2 * (y[i]-yy) == k*(i-temp))
vis[i] = 1;
}
}
bool check()
{
for (int i = 1;i <= n;i++)
if (!vis[i])return 0;
return 1;
}
int main()
{
scanf("%d", &n);
for (int i = 1;i <= n;i++)
{
scanf("%lld", &y[i]);
}
long long k[4];
k[1] = 2 * (y[2] - y[1]);
k[2 ]= (y[3] - y[1]);
k[3] = 2 * (y[3] - y[2]);
for (int i = 1;i <= 3;i++)
{
memset(vis, 0, sizeof(vis));
if (i == 1 || i == 2)
solve(k[i], 1);
else
solve(k[i], 2);
if (check())
{
puts("NO");
return 0;
}
int temp = -1;
bool flag = true;
for(int j=1;j<=n;j++)
if (vis[j] == 0)
{
if (temp == -1)
temp = j;
else
{
if (1LL * 2 * (y[j] - y[temp]) != k[i] * (j - temp))
{
flag = false;
break;
}
}
}
if (flag)
{
puts("YES");
return 0;
}
}
puts("NO");
return 0;
}
这题在写的时候为了避免浮点数(斜率),博主在分类的时候特地把3个斜率都乘以2,类似的小细节读者可以学习学习~
C题题意:给你一个正整数k,要求你组成一个字符串,并且组成该字符串的最小花费为k,输出该字符串。
组成字符串的花费是这样算的:
比如要组成abababab, 它可以拆成s=”abab”和t=”abab”,那么把这两个小字符串拼接的花费为
∑c∈(′a′,′b′,′c′..′z′)
f(s,c)*f(t,c)
f(s,c)的值为c在s中出现的次数。
思路:其实仔细想想就发现,每个字符串的值其实没有大小之分,是一个固定的值。那么我们只要想一个通用的方法去构造就好。
很容易想到先一直输出a,那么花费cost=1+2+3+4…,直到超过k,我们再用b,同样一直输出b。。。
这个方法是没问题的,然而需要值得注意的是,当输入0的时候要输出一个a,不然会WA3(哭)
#include<iostream>
using namespace std;
int n;
void solve()
{
//char tempc = 'a';
int cnt = 0;
int temp = 0;
for (char tempc = 'a';tempc <= 'z';tempc++)
{
if (cnt == n)break;
printf("%c", tempc);
temp = 0;
while (cnt < n)
{
temp++;
if (cnt + temp > n)break;
cnt += temp;
printf("%c", tempc);
}
}
cout << endl;
}
int main()
{
cin >> n;
if (n == 0) { cout << "a" << endl;return 0; }
solve();
return 0;
}
D题题意:分别有从横坐标,纵坐标开始出发的点,如果两个点会相撞,两个点的方向会交换,给你两个边界(上边界和右边界),问最终所有的点会到哪里去。。
思路:博主的第一思路竟然又是模拟。。
因为博主曾经做过一道差不多的题,也是很多点运动,首先计算出所有点相撞的时刻,然后把时间从小到大排序,然后到那个时刻就将两个点的编号交换即可,博主试着写了下,其实可行,就是点太多了,在第9个样例TLE了。试想如果横坐标有50000个点,纵坐标有50000个点,每个点都可以相撞,那么就有50000*50000个事件,很可怕。。超时是必然的。
暴力不可行,那么怎么办呢,其实只要在图纸上把题目给的样例模拟一遍,就会发现规律了,读者可以结合代码观察观察。
代码如下:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 2e5 + 5;
struct node
{
int idx, p;
bool operator<(const node &b)const
{
return p < b.p;
}
}ver[100005], hor[100005];
int vcnt, hcnt;
vector<node>V1[maxn], V2[maxn];
int tempx[100005], tempy[100005];
int ansx[100005], ansy[100005];
int main()
{
int n, w, h;
cin >> n >> w >> h;
int g,p,t;
for (int i = 1;i <= n;i++)
{
scanf("%d", &g);
if (g == 1)
{
vcnt++;
scanf("%d%d", &p, &t);
V1[p - t + 100000].push_back({ i,p });
}
else
{
hcnt++;
scanf("%d%d", &p, &t);
V2[p - t + 100000].push_back({ i,p });
}
}
for (int i = 0;i < maxn;i++)
{
sort(V1[i].begin(), V1[i].end());
sort(V2[i].begin(), V2[i].end());
}
for (int i = 0;i < maxn;i++)
{
int cnt = 0;
for (int j = 0;j < V1[i].size();j++)
{
tempx[cnt] = V1[i][j].p;
tempy[cnt] = h;
cnt++;
}
for (int j = V2[i].size() - 1;j >= 0;j--)
{
tempx[cnt] = w;
tempy[cnt] = V2[i][j].p;
cnt++;
}
cnt = 0;
for (int j = V2[i].size() - 1;j >= 0;j--)
{
ansx[V2[i][j].idx] = tempx[cnt];
ansy[V2[i][j].idx] = tempy[cnt];
cnt++;
}
for (int j = 0;j < V1[i].size();j++)
{
ansx[V1[i][j].idx] = tempx[cnt];
ansy[V1[i][j].idx] = tempy[cnt];
cnt++;
}
}
for (int i = 1;i <= n;i++)
{
printf("%d %d\n", ansx[i], ansy[i]);
}
return 0;
}
E题题意:给你n个数,数的值代表该点的颜色,有m个询问
如果询问为1 p x则是把p位置的点的值变成x
如果是2 l r 则是询问l到r的”memory”,memory计算如下.
如果序列为1 2 3 1 3 2 1
询问2 3 7
从3到7 为1的终点减起点也就是7-4=3
2:6-6=0
3:5-3=2
所以memory为5
思路:这题需要转换一下,消除差异性,也就是颜色带来的区别。如果[l,r]中颜色为x的位置有p[1],p[2],p[3]…p[k],令p[1] < p[2]< p[3]< …< p[k],则该序列颜色x提供的”memory”为p[k]-p[1],而p[k]-p[1]=(p[k]-p[k-1])+(p[k-1]-p[k-2])+…+(p[2]-p[1]).
所以我们可以建立一个pair b,b[i]=(prev[i],i-prev[i]).
prev[i]=maxj (1<=j< i&&a[j]=a[i])
那么我们要求的是
∑i∈[l,r],b[i].first>=l
b[i].second
这个式子读者可以理解一下,其实很简单。关键怎么写。线段树?那么怎么建树?不管怎么样,线段树只能保证一个区间中的值,可是这个求和有两个区间啊。
这里就要写个树套树了。
第一层区间用权值线段树动态开点
第二层区间用树状数组做。
所以它的含义到底是啥?
博主的理解就是:以b[i].first为pre的点中建一个线段树,线段树中维护b[i].second的和,也就是我们要求的。
询问则是query(v,v)-query(u-1,v),该式子表示的就是以[u,v]中任意一个为pre的点中的1到v的权值。为什么是1到v而不是u到v?因为以u到v为pre的值全部都大于u,所以1到u的点实际上对这个权值没有贡献的,所以这样求的结果与实际答案是一样的。
代码如下:
#include<iostream>
#include<set>
using namespace std;
const int maxn = 100005;
const int maxm = 10000005;
int a[maxn];
set<int>S[maxn];
#define lowbit(x) ((x)&(-x))
typedef long long ll;
int rt[maxn];
int n, m;
int Left[maxm], Right[maxm];
int cnt;
ll Sum[maxm];
void Update(int L, int C, int l, int r, int &tr)
{
if (!tr)
tr = ++cnt;
Sum[tr] += C;
if (l == r)return;
int mid = l + r >> 1;
if (L <= mid)Update(L, C, l, mid, Left[tr]);
else Update(L, C, mid + 1, r, Right[tr]);
}
void modief(int pre, int now, int d)
{
while (pre<=maxn)
{
Update(now, d, 1, n, rt[pre]);
pre += lowbit(pre);
}
}
void add(int col, int idx)
{
S[col].insert(idx);
auto it = S[col].find(idx),pre=it,suf=it;
bool flag1 = false, flag2 = false;
if (S[col].begin() != it)
{
pre--;
modief(*pre, *it, *it - *pre);
flag1 = true;
}
suf++;
if (S[col].end() != suf)
{
modief(*it, *suf, *suf - *it);
flag2 = true;
}
if (flag1&&flag2)
{
modief(*pre, *suf, -(*suf - *pre));
}
}
void del(int idx)
{
int col = a[idx];
auto it = S[col].find(idx), pre = it, suf = it;
bool flag1 = false, flag2 = false;
if (it != S[col].begin())
{
pre--;
modief(*pre, *it, -(*it - *pre));
flag1 = true;
}
suf++;
if (suf != S[col].end())
{
modief(*it, *suf, -(*suf - *it));
flag2 = true;
}
if (flag1&&flag2)
{
modief(*pre, *suf, *suf - *pre);
}
S[col].erase(*it);
}
ll ask(int L, int R, int l, int r, int &rt)
{
if (!rt || l == L&&r == R)return Sum[rt];
int mid = l + r >> 1;
if (R <= mid)return ask(L, R, l, mid, Left[rt]);
if (L > mid)return ask(L, R, mid + 1, r, Right[rt]);
return ask(L, mid, l, mid, Left[rt]) + ask(mid+1, R, mid + 1, r, Right[rt]);
}
ll query(int a, int b)
{
ll ret = 0;
while (a)
{
ret += ask(1, b, 1, n, rt[a]);
a -= lowbit(a);
}
return ret;
}
int main()
{
cin >> n >> m;
for (int i = 1;i <= n;i++)
{
scanf("%d", &a[i]);
add(a[i], i);
}
int o, x, y;
for (int i = 1;i <= m;i++)
{
scanf("%d%d%d", &o, &x, &y);
if (o == 1)
{
del(x);
a[x] = y;
add(y, x);
}
else
{
printf("%lld\n", query(y, y) - query(x - 1, y));
}
}
return 0;
}
这题只能说很难,博主虽然搞清楚了,但可能讲不清楚,如果没明白有疑问的话,欢迎留言。。