2021南京(重温经典)
2021南京(重温经典)
- 导语
- 涉及的知识点
- 题目
-
-
- C
- D
- J
-
- 参考文献
导语
打的不好,本来以为训练了这么长时间会有不少效果,但是被打的稀里哗啦,思维没有打开
涉及的知识点
思维,树状数组,记忆化搜索
链接:The 2021 ICPC Asia Nanjing Regional Contest (XXII Open Cup, Grand Prix of Nanjing)
题目
C
题目大意:给出一个序列 a 1 , a 2 , … a n a_1,a_2,\dots a_n a1,a2,…an和一个整数 k k k,现在可以对序列的任意长度的区间都加上这个 k k k,但最多只能执行一次(也可以不进行这个操作),求出操作之后得到的众数的最大个数
思路:对于每一个 x x x和 x − k x-k x−k考虑构造,因为彼此间会产生影响,设 s u m [ i ] [ 0 ] sum[i][0] sum[i][0]为前i中x的个数, s u m [ i ] [ 1 ] sum[i][1] sum[i][1]为x-k个数,那么对于一个区间 [ l , r ] [l,r] [l,r],那么从x-k变到x得到的贡献为 s u m [ r ] [ 1 ] − s u m [ l − 1 ] [ 1 ] sum[r][1]-sum[l-1][1] sum[r][1]−sum[l−1][1],这中间减少的x的贡献为 s u m [ r ] [ 0 ] − s u m [ l − 1 ] [ 0 ] sum[r][0]-sum[l-1][0] sum[r][0]−sum[l−1][0],那么可以得到挑选当前区间得到的总贡献:
s u m [ l e n ] [ 0 ] + s u m [ r ] [ 1 ] − s u m [ l − 1 ] [ 1 ] − s u m [ r ] [ 0 ] + s u m [ l − 1 ] [ 0 ] sum[len][0]+sum[r][1]-sum[l-1][1]-sum[r][0]+sum[l-1][0] sum[len][0]+sum[r][1]−sum[l−1][1]−sum[r][0]+sum[l−1][0]
移项可得
s u m [ l e n ] [ 0 ] + s u m [ r ] [ 1 ] − s u m [ r ] [ 0 ] + s u m [ l − 1 ] [ 0 ] − s u m [ l − 1 ] [ 1 ] sum[len][0]+sum[r][1]-sum[r][0]+sum[l-1][0]-sum[l-1][1] sum[len][0]+sum[r][1]−sum[r][0]+sum[l−1][0]−sum[l−1][1]
这样在处理r的时候也就顺带获得了l-1的数值,详见代码
代码
#include #define int long longusing namespace std;const int N=4e6+5;const int M=1e6+9;const int offset=2e6;int n,k,ans,cnt,a[M],mx,sum[M][2];vector<int>v[N];signed main() { ios::sync_with_stdio(0); cin.tie(0); cin >>n>>k; for(int i=1; i<=n; i++) { cin >>a[i]; a[i]+=offset; v[a[i]].push_back(a[i]),v[a[i]+k].push_back(a[i]); //a[i]在这个位置有一个值,a[i]+k可以在这个位置有一个值 mx=max(mx,max((int)v[a[i]].size(),(int)v[a[i]+k].size()));//记录初始的最大值 } if(!k) { cout <<mx/2<<endl;//会重复计算,需要去重 return 0; } for(int i=0; i<=4e6; i++) { int len=v[i].size();//获得数量 if(len==0)continue; for(int j=0; j<len; j++) {//遍历其每一个位置 sum[j+1][0]=sum[j][0]+(v[i][j]==i);//记录前j个位置有多少个i sum[j+1][1]=sum[j][1]+(v[i][j]!=i);//记录前j个位置有多少个i-k } int tmp=0; ans=max(ans,sum[len][0]);//获得最大值 for(int j=1; j<=len; j++) { tmp=max(tmp,sum[j-1][0]-sum[j-1][1]);//获得公式后缀最大值 ans=max(ans,sum[len][0]+sum[j][1]-sum[j][0]+tmp);//获得公式最值 } } cout <<ans<<endl; return 0;}
D
题目大意:略
思路:对于每个长度的第一轮循环之后,结果是将其严格单增序列相对后移(这里的相对后移指的是单增序列的相对位置),之后第2,3…轮排序的交换次数就是前i个比a[i]大的个数(去重)
因此为了获得最后的解,在扫描的时候可以直接在线处理,保证每次只获得当前长度下第一次循环的结果,当遇到a[i]>a[1]时,就交换a[i]和a[1]的位置,这就是一次必要的交换,计数器增加,然后对于每个在线处理录入的a[i],统计先前比它大的个数,直接加上即可
但是,当录入的a[i]与a[1](当前最大值)相等(假设值为x)并且后面存在比a[1]更大的数(假设值为y),那么在后面的长度当中,第一轮排序会使得当前的a[i]的值不变,而a[1]对应的值被移到了更大的数位置上,就是这样的情况:x,x,y->y,x,x,那么在插入y之后,由于y前移了,y对x也有一个需要交换的贡献,举例如下,假设当前长度构造的序列为
x , a , a , a , x , x − 1 , x − 2 , x − 1 x,a,a,a,x,x-1,x-2,x-1 x,a,a,a,x,x−1,x−2,x−1
插入y之后,由于y大于x
y , a , a , a , x , x − 1 , x − 2 , x − 1 , x y,a,a,a,x,x-1,x-2,x-1,x y,a,a,a,x,x−1,x−2,x−1,x
那么对于 x , x − 1 , x − 2 , x − 1 x,x-1,x-2,x-1 x,x−1,x−2,x−1这几个数,都需要加上 y y y的这个贡献,所有需要增加一个计数器,记录第二个x到y间有几个数,插入y的时候结果加上计数器值即可
代码
#include #define int long longusing namespace std;const int maxn=1e5+5;int n,a[maxn],t,tree[maxn];bool vis[maxn];int lowbit(int x) { return x&(-x);}void Add(int x) { for(int i=x; i<=n; i+=lowbit(i)) tree[i]++;}int Sum(int x) { int ans=0; for(int i=x; i; i-=lowbit(i)) ans+=tree[i]; return ans;}signed main() { ios::sync_with_stdio(0); cin.tie(0); cin >>t; while(t--) { cin >>n; int res=0,cnt=0; bool flag=0; memset(tree,0,sizeof(tree)); memset(vis,0,sizeof(vis));//初始化 for(int i=1; i<=n; i++)//录入数据 cin >>a[i]; cout <<0;//第一个结果 vis[a[1]]=1;//代表已经访问过 Add(a[1]);//加进来 for(int i=2; i<=n; i++) { if(!vis[a[i]])vis[a[i]]=1,Add(a[i]);//未访问插入 if(a[i]==a[1])flag=1;//标记存在多个最值 cnt+=flag-(flag?a[i]>a[1]:0);//未统计的贡献 if(a[i]>a[1])res+=1+cnt,swap(a[1],a[i]),cnt=flag=0; //a[1]永远只存最大的数字 res+=Sum(a[1])-Sum(a[i]);//在i之前出现的比a[i]大的数字个数 cout <<" "<<res; } cout <<endl; } return 0;}
J
题目大意:给出两个正整数a,b,每次可以进行三种操作:都+1,都-1,都除以一个它们共同的质数因子,判断最少多少次能使得其中一个数为1
思路:如果不进行除法操作,那么ab差值是固定的,如果存在一个数能够整除a和b,那么一定也能整除它们的差值,那么就可以将其差值进行质因子分解,将ab中的较小数加/减到距离质因子最小的数,对a,b及差值同时除质因子,以此反复即可,当无质因子时只能一直减了
代码
#include #define int long longusing namespace std;const int maxn=1e5+5;int n,a,b,t;vector<int>v;unordered_map<int,int>level;int gethash(int b,int c) {//哈希 return b*1e9+c;}int DFS(int b,int c) { if(b==1)return 0; if(c==1)return b-1;//差值为1,只能一直减 if(level[gethash(b,c)])return level[gethash(b,c)];//记忆化搜索 int mn=b-1;//至少需要的次数 for(auto i:v)//遍历所有的质因子 if(c%i==0) {//如果还没有除过 int ans=b%i;//获得差距 mn=min({mn,ans+1+DFS(b/i,c/i),i-ans+1+DFS(b/i+1,c/i)}); //分别是b大于i的情况和b小于i的情况,+1是因为进行了一次除 } return level[gethash(b,c)]=mn;}signed main() { ios::sync_with_stdio(0); cin.tie(0); cin >>t; while(t--) { cin >>a>>b; if(a<b)swap(a,b);//强制b为小值 int c=a-b; v.clear(); for(int i=2; i<=c/i; i++) if(c%i==0) {//获得质因子 v.push_back(i); while(c%i==0)c/=i; } if(c>1)v.push_back(c);//最后除剩的也是质因子 cout <<DFS(b,a-b)<<endl;//记忆化搜索 } return 0;}
参考文献
- 2021 46届icpc 南京
- 【题目记录】——ICPC南京2021