水题解
- 随便拉了几道题,感觉区域赛签到难度,cf1600-1700,写点题解。
MEX maximizing
题意:就是给你两个数q,x,接着q行数,每行代表像一个数组中插入这个数(初始数组为空)
然后你还可以在任意时刻对数组中的任意一个数的加减任意x倍数,问数组中最小没出现的数是多少,可以为0
思路:我的做法就是维护一个数mod x未出现的最小倍数,然后每次插入一个数,就mod x 在最小倍数插入这个数,然后最小倍数++,然后我们查询最小未出现的数是利用树状数组,如果查询前缀和(插入的数为1) c[x] == x 说明前x个数都插满了,我们倍增找出第一个不满足条件的数就是查询的答案。
另外朋友提供了权值线段树的思路,就是维护一个 1 − x + 1 1-x+1 1−x+1的权值线段树,节点存入的是这个数出现的最小倍数,然后区间在维护最小,插入就是将数 m o d x + 1 mod x + 1 modx+1位置上
AC代码
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define lowbit(x) (x&(-x))
using namespace std;
typedef long long ll;
const int maxn = 8e5+5;
const int N = 4e5+5;
ll c[maxn];
ll ct[maxn];
int q;
void change(ll v,ll pos){
while(pos<=q){
c[pos] += v;
pos += lowbit(pos);
}
}
int main(){
int x;
scanf("%d%d",&q,&x);
int p = log2(q);
// for(int i = 0;i<=q+10;i++)ct[i] = 1;
int flag = 0;
for(int i = 0;i < q; i++){
int y;
scanf("%d",&y);
ll mod = y % x;
if(mod==0&&flag==0){
flag = 1;
ct[0]++;
}
else {
if(ct[mod]*x+mod <= q)change(1,ct[mod]*x+mod);
ct[mod]++;
}
if(flag==0){
printf("0\n");
continue;
}
else {
ll sum = 0;
int pos = 0;
for(int j = p;j>=0;j--){
// cout<<sum+c[(pos+(1<<j))]<<" "<<pos + (1<<j)<<c[(pos+(1<<j))]<<endl;
if((pos+(1<<j))<=q&&sum+c[(pos+(1<<j))] == (pos + (1<<j))){
sum += c[pos+(1<<j)];
pos += (1<<j);
}
}
printf("%d\n",pos+1);
}
}
}
Number of Ways
题意:将数组分成三份,然后问有多少种分法
思路:没想到数组是有序的,我想当然的以为数组是无序,误以为是个dp……谁知道就是思维,就是统计一下数组和的2/3倍之前有多少个数组和的1/3的位置,然后数组不能除以三输出答案0
AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5+5;
typedef long long ll;
ll sum[maxn];
// int a[maxn];
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i<=n;i++){
ll x;
scanf("%lld",&x);
sum[i] = sum[i-1] + x;
}
if(sum[n]%3!=0){
printf("0");return 0;
}
ll cnt1 = 0;
ll ans = 0;
for(int i = 1;i < n;i++){
if(sum[n]/3*2==sum[i]){
ans += cnt1;
}
if(sum[n]/3==sum[i]){
cnt1++;
}
}
cout<<ans;
}
Reading Books (easy version)
题意:有一堆书,两个人,读这一堆书,然后,每个书给定可以给A读,B读,或者一起读,然后每个数给定阅读时间,每个人都有一个做小阅读书目,然后问怎么选花时间最小(如果A和B看一本书时间就算一倍)
思路:我可能做的稍微有些麻烦,建立三个小根堆,然后存A可读,B可读,AB可读的书
然后如果A队头的加B堆头小于C堆头,就选用A和B的书(弹出队),否则选一起读的书(弹出队),然后在判一些细节
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
priority_queue<int, vector<int> ,greater<int> >q1,q2,q3;
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i = 0;i<n;i++){
int t,a,b;
scanf("%d%d%d",&t,&a,&b);
if(a==1&&b==1){
q3.push(t);
}
else if(a==1)q1.push(t);
else if(b==1)q2.push(t);
}
if(min(q1.size(),q2.size())+q3.size()<k){
printf("-1");return 0;
}
ll ans = 0;
int cnt = 0;
while(cnt<k){
if(q3.empty()){
ans += (q1.top()+q2.top());
q1.pop();q2.pop();
}
else if(q1.empty()||q2.empty()){
ans += (q3.top());
q3.pop();
}
else if((q1.top()+q2.top())<q3.top()){
ans += (q1.top()+q2.top());
q1.pop();q2.pop();
}
else {
ans += q3.top();
q3.pop();
}
cnt++;
}
cout<<ans<<endl;
}
Linova and Kingdom
题意:是一个比较树上的问题,就是给一个树,然后一棵树上有两种节点,一个种工业节点,另一个种是旅游节点,然后我们我们在1节点召开会议,邀请所有工业节点的人来,然后我们我们要使来的人途径旅游节点的总和最大,问,要把那些节点设置为工业节点(设置k个工业节点)。
思路:我第一印象就是将叶子节点先设置为工业节点,但是多余的节点该怎么办,开始我以为是当然要设置最深的节点,但是我又考虑到每个节点子树大小的问题,比如一个节点子树很大,那么我们在这个节点设置工业节点就很不值,于是最后我想到了设置节点的代价,节点深度-子树大小,然后排个序,把最大的k个设为工业节点就好了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
struct edge{
int v,next;
}e[maxn<<1];
int head[maxn],cnt = 0;
void add(int u,int v){
e[cnt].v = v;
e[cnt].next = head[u];
head[u] = cnt++;
}
struct Sz{
int sz,id,deep;
}dp[maxn];
int deep[maxn],vis[maxn];
void dfs(int u,int fa){
dp[u].sz = 1;
dp[u].id = u;
dp[u].deep = dp[fa].deep + 1;
for(int i = head[u];~i;i=e[i].next){
int v = e[i].v;
if(v==fa)continue;
dfs(v,u);
dp[u].sz += dp[v].sz;
}
}
bool cmp(Sz a,Sz b){
// if(a.sz==b.sz)return a.deep>b.deep;
// return a.sz<b.sz;
return a.deep - a.sz > b.deep - b.sz;
}
ll ans = 0;
void dfs2(int x,int fa){
if(vis[x]==0)deep[x] = deep[fa] + 1;
else {
deep[x] = deep[fa];
ans += deep[x];
}
for(int i = head[x];~i;i=e[i].next){
int v = e[i].v;
if(v==fa)continue;
dfs2(v,x);
}
}
int main(){
int n,k;
memset(head,-1,sizeof head);
scanf("%d%d",&n,&k);
for(int i = 0;i < n-1;i++){
int v,u;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(1,0);
sort(dp+1,dp+n+1,cmp);
for(int i = 1;i <= k;i++){
vis[dp[i].id] = 1;
}
dfs2(1,0);
cout<<ans<<endl;
}
Hard problem
题意:给你n个字符串,然后问你将这n个按字典序排序花费的最小代价是多少,不可以改变字符串的顺序,但是可以翻转字符串,翻转第k个字符串的代价为a[k],然后求最小代价
思路:dp,dp[i][j], d p [ i ] [ 0 ] dp[i][0] dp[i][0]代表排到第i个字符串,并且第i个字符串为正序话费的最小代价, d p [ i ] [ 1 ] dp[i][1] dp[i][1]代表排到第i个字符串,然后第i个字符串为反序最小的代价
#include <bits/stdc++.h>
// #define INF 1e16
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
int c[maxn];
string str[maxn];
string rs[maxn];
ll dp[maxn][2];
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&c[i]);
}
str[0] = "";
// memset(dp,INF,sizeof dp);
for(int i = 0;i <= n;i++){
dp[i][0] = dp[i][1] = 1e16;
}
dp[0][1] = dp[0][0] = 0;
for(int i = 1;i<=n;i++){
cin>>str[i];
rs[i] = str[i];
reverse(rs[i].begin(),rs[i].end());
// cout<<rs[i]<<"##"<<endl;
if(str[i]>=str[i-1]){
dp[i][0] = min(dp[i-1][0],dp[i][0]);
}
if(str[i]>=rs[i-1]){
dp[i][0] = min(dp[i-1][1],dp[i][0]);
}
if(rs[i]>=str[i-1]){
dp[i][1] = min(dp[i-1][0] + c[i],dp[i][1]);
}
if(rs[i]>=rs[i-1]){
dp[i][1] = min(dp[i-1][1] + c[i],dp[i][1]);
}
}#include <bits/stdc++.h>
using namespace std;
const int maxn = 205;
int a[maxn],b[maxn];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
}
for(int i = 1 ;i <= m;i++){
scanf("%d",&b[i]);
}
for(int i = 0;i<(1<<9);i++){
int flag = 0;
for(int j = 1;j <= n;j++){
int f2 = 0;
for(int k = 1;k <= m;k++){
if(((a[j]&b[k])|i)==i){
f2 = 1;break;
}
}
if(f2==0){
flag = 1;break;#include <bits/stdc++.h>
using namespace std;
const int maxn = 205;
int a[maxn],b[maxn];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
}
for(int i = 1 ;i <= m;i++){
scanf("%d",&b[i]);
}
for(int i = 0;i<(1<<9);i++){
int flag = 0;
for(int j = 1;j <= n;j++){
int f2 = 0;
for(int k = 1;k <= m;k++){
if(((a[j]&b[k])|i)==i){
f2 = 1;break;
}
}
if(f2==0){
flag = 1;break;
}
}
if(flag == 0){
printf("%d",i);return 0;
}
}
}
}
}
if(flag == 0){
printf("%d",i);return 0;
}
}
}
if(min(dp[n][0],dp[n][1])>=1e16){
printf("-1\n");
}
else printf("%lld",min(dp[n][0],dp[n][1]));
}
Good Subarrays
题意:公式题,给一个序列要求求出满足 ∑ i = l r a i = r − l + 1 \sum\limits_{i=l}^{r} a_i = r - l + 1 i=l∑rai=r−l+1的子序列个数
思路:转化一步原式等于 s u m i − s u m j = i − j sum_i -sum_{j}=i-j sumi−sumj=i−j
然后在转化一步:
s
u
m
i
−
i
=
s
u
m
j
−
j
sum_i-i=sum_{j}-j
sumi−i=sumj−j
我们直接算一个 s u m [ i ] − i sum[i]-i sum[i]−i数组,然后找相等的,算个组合数
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
ll sum[maxn];
int a[maxn];
map<int,ll>mp;
int main(){
int t,n;
scanf("%d",&t);
while(t--){
// getchar();
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%1d",&a[i]);
}
mp.clear();
// cout<<"k"<<endl;
for(int i = 1;i <= n;i++){
sum[i] = sum[i-1] + a[i];
}
for(int i = 1;i <= n;i++){
sum[i] -= i;
}
for(int i = 0;i <= n;i++){
mp[sum[i]] += 1;
}
ll ans = 0;
for(auto i :mp){
ans += i.second*(i.second-1)/2;
}
printf("%lld\n",ans);
}
}
Orac and LCM
- CodeForces - 1349A
题意:询问一堆数两两lcm的所有数的gcd
思路:数论我不擅长,然后具体看别的题解说的就不写思路了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
int a[maxn];
vector<ll>fat[maxn<<1];
ll Pow(ll a,ll b){
ll ans = 1;
while(b){
if(b&1)ans = ans * a;
b>>=1;
a*=a;
}
return ans ;
}
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
}
for(int i = 1;i <= n;i++){
// int tmp = a[i];
for(int j = 2;j * j <= a[i];j++){
// cout<<a[i]<<" "<<j<<" "<<tmp<<endl;
int num = 0;
while(a[i]%j==0){
a[i] /= j;
num++;
}
if(num!=0){
// cout<<num<<endl;
fat[j].push_back(num);
}
}
if(a[i]!=1){
fat[a[i]].push_back(1);
}
}
long long ans = 1;
for(int i = 2;i < (maxn<<1);i++){
if(fat[i].size() == n){
sort(fat[i].begin(),fat[i].end());
ans *= Pow(i,fat[i][1]);
}
else if(fat[i].size() == n-1){
sort(fat[i].begin(),fat[i].end());
ans *= Pow(i,fat[i][0]);
}
}
printf("%lld",ans);
}
Anna, Svyatoslav and Maps
题意:给一个图,然后再给一个节点序列,求一个最短的序列,使得原序列是这个求得序列的最短路
思路:由于图并不大,所以先floyd预处理所有的两点间最短路,然后按照原数列走一遍,比如我们有一个最近的点在我们选取的点u,如果我们找到第i个点 d [ u ] [ i − 1 ] + d [ i − 1 ] [ i ] > d [ u ] [ i ] d[u][i-1] + d[i-1][i] > d[u][i] d[u][i−1]+d[i−1][i]>d[u][i]说明我们需要选第i-1的点
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 105;
const int maxn = 1e6+6;
int a[N][N];
int dis[N][N];
int c[maxn];
vector<int> ans;
int main(){
memset(dis,INF,sizeof dis);
int n;
scanf("%d",&n);
for(int i = 1;i <= n; i++){
for(int j = 1;j <= n;j++){
int x;
scanf("%1d",&x);
if(x==1)dis[i][j] = 1;
}
}
for(int i = 0;i<=n;i++){
dis[i][i] = 0;
}
// for(int i = 1;i <= n; i++){
// for(int j = 1;j <= n;j++){
// cout<<a[i][j];
// }
// cout<<endl;
// }
for(int k = 1;k <= n;k++){
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
if(dis[i][j]>dis[i][k]+ dis[k][j]){
dis[i][j]=dis[i][k] + dis[k][j];
}
}
}
}
int m;
scanf("%d",&m);
for(int i = 1 ;i <= m;i++){
scanf("%d",&c[i]);
}
// printf("%d",c[1]);
int las = c[1];
ans.push_back(las);
for(int i = 2;i <= m;i++){
// cout<<las<<"#"<<c[i]<<" "<<dis[las][c[i]] << " "<<dis[las][c[i-1]] + dis[c[i-1]][c[i]]<<endl;
if(dis[las][c[i]] < dis[las][c[i-1]] + dis[c[i-1]][c[i]]){
// printf(" %d",c[i-1]);
ans.push_back(c[i-1]);
las = c[i-1];
}
}
ans.push_back(c[m]);
cout<<ans.size()<<endl;
for(auto i : ans){
printf("%d ",i);
}
}
Boboniu Chats with Du
题意:有n天一个男生给女生发QQ如果然后女生对男生每天有一个厌烦值,男生每天可以选择法或不发,但是如果都一天男生的厌烦度超过了女生的阈值,女生就要禁言了男生k天,k也给定,问男生如何选择才生使女生积累的厌烦值最大
思路:枚举一下男生超过女生阈值的天数,假如有2个,那么我们安排俩小弟在这两天就可以了,模拟一下
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
int bg[maxn],sm[maxn];
ll sum[maxn];
ll sum2[maxn];
int cmp(ll x,ll y){
return x>y;
}
int main(){
int n,d,m;
cin>>n>>d>>m;
int cnt1=1,cnt2=1;
for(int i = 1;i <= n; i++){
int x;
scanf("%d",&x);
if(x<=m)sm[cnt2++] = x;
else bg[cnt1++] = x;
}
sort(bg+1,bg+cnt1,cmp);
for(int i = 1;i < cnt1;i++){
sum[i] = sum[i-1] + bg[i];
}
sort(sm+1,sm+cnt2,cmp);
for(int i = 1;i < cnt2;i++){
sum2[i] = sum2[i-1] + sm[i];
// cout<<sum2[i]<<"#"<<endl;
}
int mx = (n-1)/(d+1)+1;
ll ans = 0;
for(int i = 0;i < cnt1;i++){
ll p=1ll*(i-1)*d+i;
if(p>n)continue;
if(i==0)ans = max(ans,sum2[cnt2-1]);
else if((1LL*(i-1)*d -(cnt1-1-i))> 0){
ans = max(ans,sum[i] + sum2[cnt2 -1- (1LL*(i-1)*d-(cnt1-1-i))]);
}
else {
ans = max(ans,sum[i] + sum2[cnt2-1]);
}
}
cout<<ans<<endl;
}
Cow and Snacks
题意:一共有1-n个点心,每个人喜欢吃两个点心,如果一个人喜欢吃的两个点心都被别人吃了,那么他就会sad,然后问sad人最小是多少
思路:就是一个人喜欢吃的两个点心相连,我们发现,一旦出现一个环sad人数++,并查集记录一下就好了
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int fa[maxn];
int get(int x){
if(fa[x]==x)return x;
return fa[x] = get(fa[x]);
}
int merge(int x,int y){
int p = get(x);
int q = get(y);
if(p!=q){
fa[p] = q;
return 1;
}
return 0;
}
int main(){
int n,k;
int ans = 0;
scanf("%d%d",&n,&k);
for(int i = 0 ;i <= n;i++)fa[i] = i;
for(int i = 1;i <= k;i++){
int x,y;
scanf("%d%d",&x,&y);
if(!merge(x,y)){
ans++;
}
}
printf("%d",ans);
}
Boboniu and Bit Operations
- CodeForces - 1395C
题意:给你两个数组a,b然后a中每个元素在b中选取一个元素,然后两个元素向&,然后得到的所有结果向|,得到最后的答案最大是多少
思路:枚举答案,因为最终结果不超过1e9,然后就是,我们判断结果是否可能是这个数,就是需要
#include <bits/stdc++.h>
using namespace std;
const int maxn = 205;
int a[maxn],b[maxn];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
}
for(int i = 1 ;i <= m;i++){
scanf("%d",&b[i]);
}
for(int i = 0;i<(1<<9);i++){
int flag = 0;
for(int j = 1;j <= n;j++){
int f2 = 0;
for(int k = 1;k <= m;k++){
if(((a[j]&b[k])|i)==i){
f2 = 1;break;
}
}
if(f2==0){
flag = 1;break;
}
}
if(flag == 0){
printf("%d",i);return 0;
}
}
}
Eugene and an array
题意:就是给一个串,问这个串有多少字串的和不是0
思路:先处理一个前缀和,然后在每个位置判断这个位置之前第一个前缀和等于自身的点,然后答案加中间的数,类似尺取的方法
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
int a[maxn];
ll sum[maxn];
map<ll,int>mp;
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i <= n ;i++){
scanf("%d",&a[i]);
}
for(int i = 1;i <= n;i++){
sum[i] = sum[i-1] + a[i];
}
ll ans = 0;
// mp.clear();
mp[0] = 1;
int l = 0;
for(int i = 1;i <= n; i++){
ans += i - max(l,mp[sum[i]]);
l = max(l,mp[sum[i]]);
mp[sum[i]] = i+1;
}
cout<<ans<<endl;
}