CF2275(Div.3)做题记录
A. In Search of Convenience
*800
数据很小,暴力枚举。
Code:
#include<bits/stdc++.h>
using namespace std;
int T;
int main(){
scanf("%d",&T);
while(T--){
int xx,yy;
scanf("%d%d",&xx,&yy);
int R;
scanf("%d",&R);
int ansi=0,ansj=0;
for(int i=0;i<=25;i++){
for(int j=0;j<=25;j++){
if(i*i+j*j==R*R){
ansi=i,ansj=j;
}
}
}
printf("%d %d\n",xx+ansi,yy+ansj);
}
return 0;
}
B. Did Not Go to Print
*800
模拟它的操作。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T;
int n;
int s[N],top;
bool vis[N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
top=0;
memset(vis,0,(n+1)*sizeof(bool));
int tot=n;
for(int i=1;i<=n;i++){
int x;
scanf("%1d",&x);
if(x==1)s[++top]=i;
else if(x==2){
if(top){
vis[s[top]]=true;
top--;
}
else{
vis[i]=true;
}
tot--;
}
else{
vis[i]=true;
tot--;
}
}
printf("%d\n",tot);
if(!tot)puts("");
else{
for(int i=1;i<=n;i++){
if(!vis[i])printf("%d ",i);
}
puts("");
}
}
return 0;
}
C. Unrequited Love
*1000
开桶,统计 \(a_x+a_{x+2}-a_{x+4}\) 值的数量。特判 \(x=y-4\),\(x=y-2\),\(x=y\),\(x=y+2\),\(x=y+4\),不能将它们统计在内。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int T;
int n;
int a[N],val[N];
int arr[N];
int *tong=arr+40005;
int s[N],top;
void init(){
for(int i=1;i<=top;i++){
tong[s[i]]=0;
}
top=0;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
init();
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n-4;i++){
val[i]=a[i]+a[i+2]-a[i+4];
tong[val[i]]++;
s[++top]=val[i];
}
ll ans=0;
for(int i=1;i<=n-4;i++){
ans+=tong[val[i]];
if(i-4>=1&&val[i-4]==val[i])ans--;
if(i-2>=1&&val[i-2]==val[i])ans--;
ans--;
if(i+2<=n-4&&val[i+2]==val[i])ans--;
if(i+4<=n-4&&val[i+4]==val[i])ans--;
}
printf("%lld\n",ans>>1);
}
return 0;
}
D. Precision Alignment
*1300
我们发现,只要存在 \(a_i>b_i\) 或 \(b_i>c_i\) ,就可以对它进行不停操作,\(a_i+b_i+c_i\) 就可以一直增加。不存在的有2种可能。1. \(a_i=b_i=c_i\) 。此时操作是无效的,增加不了。2. \(a_i \le b_i\) 且 \(b_i \le c_i\) ,此时操作只会让总和减小。但不断操作某一个,例如操作 \(a_i,b_i\) ,会让 \(c_i\) 不断减小,直到 \(b_i>c_i\) 时,转化为一直增加的情况。这样的代价最小是 \(2 \times \min(b_i-a_i+1,c_i-b_i+1)\) 。我们要想让所有值中最小的最大,可以二分答案,计算每个 \(i\) 还差多少次操作可以达到要求。时间复杂度 \(O(n \log k)\) 。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
const ll inf=0x3f3f3f3f3f3f3f3f;
int T;
int n;
ll k;
ll a[N],b[N],c[N];
bool check(ll mid){
ll tot=0;
for(int i=1;i<=n;i++){
ll val=a[i]+b[i]+c[i];
if(val>=mid)continue;
tot+=mid-val;
if(a[i]==b[i]&&b[i]==c[i])return false;
if(c[i]>=b[i]&&b[i]>=a[i]){
tot+=min(c[i]-b[i]+1,b[i]-a[i]+1)<<1;
}
if(tot>k)return false;
}
if(tot>k)return false;
else return true;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
}
ll l=-inf,r=inf;
while(l<r){
ll mid=(l+r+1)>>1;
if(check(mid)==true)l=mid;
else r=mid-1;
}
printf("%lld\n",l);
}
return 0;
}
E. Repentance Is Already on the Way
*1500
假设有基本的连接方式:\(a_1 \rightarrow b_1 \rightarrow a_2 \rightarrow b_2 \rightarrow ... \rightarrow a_n \rightarrow b_n\) 。我们发现,这样连接(从 \(a_1\) 开始,中间没有间隔点)总是 \(b_i \rightarrow a_{i+1}\) 。还发现,\(b_i\) 不会有 \(b_i \rightarrow a_i,a_{i-1}\) (因为我们是从 \(a_1\) 开始的,如果出现这个情况,一定会出现有 \(b_i\) 无法被连接)。那么,如果有间隔点呢?假设从某个点开始,\(a_i \rightarrow b_{i+1}\) ,那么 \(b_i\) 只能由右侧 \(a_{i+1} \rightarrow b_i\) ,形成交错的情况。这样讨论下来,就只有不间隔和交错连接两种情况。讨论两种情况的分界 \(i\) ,取最大值。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T;
int n;
int a[N],b[N];
int fval(int x,int y){
if(x==y)return 2;
else return 1;
}
int s0[N]; //左边以i结尾
int s1[N][2]; //交错以i开始/结尾
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)scanf("%d",&b[i]);
s0[1]=0;
for(int i=2;i<=n;i++){
s0[i]=s0[i-1]+fval(a[i],b[i-1])+fval(a[i-1],b[i-1]);
}
s1[n][0]=s1[n][1]=0;
for(int i=n-1;i>=1;i--){
s1[i][0]=s1[i+1][1]+fval(a[i],b[i+1]);
s1[i][1]=s1[i+1][0]+fval(b[i],a[i+1]);
}
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,s0[i]+s1[i][0]+s1[i][1]+fval(a[n],b[n]));
}
printf("%d\n",ans);
}
return 0;
}
F. Tea Blend
*1800
要求因子数量(也就是 \(d(n)\) )为奇数,这让我们想到了:如果 \(\displaystyle n=\sum_{i=1}^{t}{p_i^{k_i}}\) ,那么 \(\displaystyle d(n)=\sum_{i=1}^{t}{(k_i+1)}\) 。让 \(d(n)\) 为奇数,那 \(k_i+1\) 就不能为偶数。我们的目标就变成了:计算 \(F(i,j)\) 中所有质因子的指数,统计所有指数都不为奇数的数量。\((i,j)\) 太多了,不能枚举,但我们想到,如果 \(a_i\) 和 \(\displaystyle \prod_{k=1}^{j}{a_k}\) 在相同质因子的指数奇偶性相同,那乘起来一定都是偶数。所以,我们要统计 \(a_i\) 和 \(\displaystyle \prod_{k=1}^{j}{a_k}\) 的奇偶性情况。\(a_i \le 10^6\) ,那一共有 \(78498\) 个质数,太多了,我们想到用哈希存下情况,方便我们比较。当然,所有质因子的数量很多。我们要提前算出 \(1 \sim \max{a_i}=M\) 的质因子,时间复杂度是 \(O(M \log M)\) 的。整个程序时间复杂度大约是 \(O(n \log{n})\) 的。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod1=1e9+7,mod2=1e9+9;
const ll pm=131;
const int N=2e5+5;
const int A=1e6;
int cnt;
int p[N];
bool visp[A+5];
void prime(){
visp[1]=true;
for(int i=2;i<=A;i++){
if(!visp[i])p[++cnt]=i;
for(int j=1;j<=cnt&&1LL*i*p[j]<=A;j++){
visp[i*p[j]]=true;
if(i%p[j]==0)break;
}
}
}
int T;
int n;
int a[N];
int arr[A+5];
vector<int>t[A+5];
bool vis[N];
struct node{
ll val1,val2;
bool operator<(const node& x)const{
if(val1==x.val1)return val2<x.val2;
else return val1<x.val1;
}
};
node pw[N];
map<node,int>mp;
void init(){
for(int i=1;i<=n;i++){
for(auto j:t[a[i]]){
vis[j]=false;
}
}
mp.clear();
}
int main(){
prime();
for(int i=1;i<=A;i++)arr[i]=i;
for(int i=1;i<=cnt;i++){
for(int j=p[i];j<=A;j+=p[i]){
int tot=0;
while(arr[j]%p[i]==0){
tot++;
arr[j]/=p[i];
}
if((tot&1)==1){
t[j].emplace_back(i);
}
}
}
pw[0].val1=pw[0].val2=1;
for(int i=1;i<=cnt;i++){
pw[i].val1=(pw[i-1].val1*pm)%mod1;
pw[i].val2=(pw[i-1].val2*pm)%mod2;
}
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
ll now1=0,now2=0;
for(int i=1;i<=n;i++){
for(auto j:t[a[i]]){
if(vis[j]==false){
vis[j]=true;
now1=(now1+pw[j-1].val1)%mod1;
now2=(now2+pw[j-1].val2)%mod2;
}
else{
vis[j]=false;
now1=(now1+mod1-pw[j-1].val1)%mod1;
now2=(now2+mod2-pw[j-1].val2)%mod2;
}
}
if(!mp.count({now1,now2}))mp[{now1,now2}]=1;
else mp[{now1,now2}]++;
}
ll ans=0;
for(int i=1;i<=n;i++){
ll now1=0,now2=0;
for(auto j:t[a[i]]){
now1=(now1+pw[j-1].val1)%mod1;
now2=(now2+pw[j-1].val2)%mod2;
}
if(mp.count({now1,now2})){
ans+=mp[{now1,now2}];
}
}
printf("%lld\n",ans);
init();
}
return 0;
}
G. Copper Squander
*1900
如果我们不买线条,那答案就是最小生成树。如果买呢?可以确定,留下的也一定是最小生成树中较小的那几条边(回想一下 Kruskal 的步骤。想象有几条边我们没选,这时树会变成几个连通块。如果我们买,直接花费这个代价;如果不买,就要选出一条边,最小并连接起两个连通块,这条边肯定是最小生成树上的边)。那怎么找到要买几条边?假设我们从买 \(n-1\) 条边开始(也就是保留 \(0\) 条边),我们会有 \(\displaystyle \sum{d_i} - \frac{n(n-1)}{2}x_j\) 的收益。在此基础上,保留一条边,收益 \(-e_1+(n-1)x_j\) (假设 \(e_i\) 就是第 \(i\) 条最小生成树边的代价),保留第 \(i\) 条就会在之前的基础上得到 \(-e_i+(n-i)x_j\) 的收益。我们发现,\(-e_i\) 和 \((n-i)x_j\) 都是单调递减的。所以,想让这些收益的和最大,我们就二分找到收益由正转负的分界点,统计收益。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int T;
int n,m,q;
struct edge{
int x,y,val;
bool operator<(const edge z)const{
return val<z.val;
}
}e[N];
int fx[N];
int getf(int x){
if(fx[x]==x)return x;
int fn=getf(fx[x]);
return fx[x]=fn;
}
ll d[N],s[N];
bool check(int mid,ll x){
if(mid==0)return true;
if(-d[mid]+1LL*(n-mid)*x>=0)return true;
else return false;
}
void init(){
for(int i=1;i<=n;i++)fx[i]=i;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&m,&q);
init();
ll sum=0;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].val);
sum+=e[i].val;
}
sort(e+1,e+1+m);
int cnt=0;
for(int i=1;i<=m;i++){
int x=e[i].x,y=e[i].y;
int ux=getf(x),uy=getf(y);
if(ux==uy)continue;
fx[ux]=uy;
d[++cnt]=e[i].val;
}
for(int i=1;i<=cnt;i++){
s[i]=s[i-1]+d[i];
}
while(q--){
ll x;
scanf("%lld",&x);
ll ans=sum-1LL*n*(n-1)/2*x;
int l=0,r=cnt;
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid,x)==true)l=mid;
else r=mid-1;
}
ans=ans-s[l]+1LL*((n-1)+(n-l))*l/2*x;
printf("%lld ",ans);
}
puts("");
}
return 0;
}
H. A Problem to Warm Up the Eyebrows
*2400
每个 \(S^2=(a_1+a_2+...+a_{n*m})(a_1+a_2+...+a_{n*m})\) 。把它展开,就是许多个 \(a_i,a_j\) 相乘。不妨交换求和顺序,我们讨论选择两个点对整个答案的贡献。我们找到能框住两个点的矩形。假设两个点 \((x_1,y_1) \ ,(x_2,y_2) \ , x_1 \le x_2 \ , y_1 \le y_2\),贡献为 \(2\displaystyle \sum_{i=1}^{x_1}\sum_{j=x_2}^{n}\sum_{k=1}^{y_1}\sum_{l=y_2}^{m}{(-1)^{j-i+1+l-k+1}a_{x_1,y_1}a_{x_2,y_2}}\) 。\((-1)^{\alpha}\) 这种很有特点,上式可以转化为 \(\displaystyle 2a_{x_1,y_1}a_{x_2,y_2} \sum_{i=1}^{x_1}{(-1)^{i}} \sum_{i=x_2}^{n}{(-1)^{i}} \sum_{i=1}^{y_1}{(-1)^{i}} \sum_{i=y_2}^{m}{(-1)^{i}}\) 。这样,系数就只跟 \(x_1,x_2,y_1,y_2\) 自己有关了。我们分为3类统计:1.点在当前点左上方,左方或上方。我们利用分配律,前缀和统计这些点的贡献,在乘上自己这个点的系数。2.这样其实不能统计到所有点,还要统计点在当前点右上方。开一个从右上向左下的前缀和。3.自己相乘。此时,根据 \(S^2\) 的展开式,贡献中是没有 \(2\) 这个系数的。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e6+5;
const ll mod=1e9+7;
int T;
int n,m;
int fid(int x,int y){
return x*(m+2)+y;
}
ll a[N];
ll s1[N],s2[N];
ll f(int l,int r){
int len=r-l+1;
if((len&1)==0)return 0;
else{
if((l&1)==0)return 1;
else return -1;
}
}
void init(){
for(int i=0;i<=n;i++){
for(int j=0;j<=m+1;j++){
s1[fid(i,j)]=s2[fid(i,j)]=0;
}
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%lld",&a[fid(i,j)]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
s1[fid(i,j)]=s1[fid(i-1,j)]+s1[fid(i,j-1)]-s1[fid(i-1,j-1)]
+f(1,i)*f(1,j)*a[fid(i,j)];
s1[fid(i,j)]=(s1[fid(i,j)]%mod+mod)%mod;
}
}
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
s2[fid(i,j)]=s2[fid(i-1,j)]+s2[fid(i,j+1)]-s2[fid(i-1,j+1)]
+f(1,i)*f(j,m)*a[fid(i,j)];
s2[fid(i,j)]=(s2[fid(i,j)]%mod+mod)%mod;
}
}
ll ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
ans+=2LL*a[fid(i,j)]*f(i,n)*f(j,m)*((s1[fid(i-1,j)]+s1[fid(i,j-1)]-s1[fid(i-1,j-1)]+mod)%mod);
ans=(ans%mod+mod)%mod;
ans+=2LL*a[fid(i,j)]*f(i,n)*f(1,j)*s2[fid(i-1,j+1)];
ans=(ans%mod+mod)%mod;
ans+=a[fid(i,j)]*a[fid(i,j)]*f(1,i)*f(i,n)*f(1,j)*f(j,m);
ans=(ans%mod+mod)%mod;
}
}
printf("%lld\n",ans);
}
return 0;
}

浙公网安备 33010602011771号