「EZEC」 Round 10 庆典 div3
「EZEC-10」打分(题目)
题目大意:给出一个序列 a 1 , a 2 . . . a n a_1,a_2...a_n a1,a2...an,可以操作 m m m次,每次使 a i + 1 ( 1 ⩽ i ⩽ n ) a_i+1(1\leqslant i \leqslant n) ai+1(1⩽i⩽n),问去掉一个最大值一个最小值之后和的最大值
分析:
- 我们可以将原序列排序,因为可以去掉最小值,所以说 a 1 a_1 a1我们可以不管,剩下就是要操作 m m m次,使得去掉最大值后的和尽可能大
- 那么我们其实就要使得 a 2 − a n − 1 a_2-a_{n-1} a2−an−1尽可能大,但 不 能 大 于 a n 不能大于a_n 不能大于an,不然 a n a_n an就不是被舍掉的了,如果每个数都与 a n a_n an相等了,但 m m m还没操作完,就可以将 a n + 1 a_n+1 an+1这样就有了更大的上限,重复操作到 m m m次即可
- 其实可以把这些操作转换成算式的,如果没有使每个都等于 a n a_n an,那么就是将 a 2 . . . a n − 1 a_2...a_{n-1} a2...an−1加了 m m m,那平均数就是 ( ( ∑ i = 2 n − 1 a i ) + m ) ((\sum_{i=2}^{n-1}a_i)+m) ((∑i=2n−1ai)+m),如果不是,那么就是先将每个数补到 m m m,需要用 t t t次,那就剩下 m − t m-t m−t没操作,现在和已经是 a n ∗ ( n − 2 ) a_n*(n-2) an∗(n−2)了,所以在 a n a_n an的基础上 + ( m − t − 1 ) / ( n − 2 + 1 ) + ( m − t − 1 ) / +(m-t-1)/(n-2+1)+(m-t-1)/%(n-2+1) +(m−t−1)/(n−2+1)+(m−t−1)/将剩下的 m − t m-t m−t次操作平均分配到 a 2 . . . a n a_2...a_n a2...an里,记得有余数。
代码实现:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
typedef long long ll;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
int n,m;
int mmax=0,mmin=1<<30;
int a[100010],sum;
signed main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
read(n);read(m);
for (int i=1;i<=n;i++) read(a[i]),mmin=min(mmin,a[i]),mmax=max(mmax,a[i]),sum+=a[i];
sum-=mmax+mmin;
if (mmax*(n-2)-sum>=m) printf("%lld",(sum+m));
else m-=mmax*(n-2)-sum,sum=mmax*(n-2)+(m-1)/(n-1)*(n-2)+(m-1)%(n-1),printf("%lld",sum);
// fclose(stdin);fclose(stdout);
return 0;
}
「EZEC-10」排列排序(题目)
题目大意:给出 a 1 . . . a n a_1...a_n a1...an,每次可以将一个区间 [ l , r ] [l,r] [l,r]排序,花费 r − l + 1 r-l+1 r−l+1的代价,问使得 a a a有序的最少代价
分析:
1.我们发现如果要让 a i a_i ai回到 a a i a_{a_i} aai的位置,那么至少要花费 ∣ a i − a a i + 1 ∣ |a_i-a_{a_i}+1| ∣ai−aai+1∣的代价,那么其实就是每次将 m i n ( a i , a a i ) min(a_i,a_{a_i}) min(ai,aai)~ m a x ( a i , a a i ) + 1 max(a_i,a_{a_i})+1 max(ai,aai)+1,之后的需要操作的区间总长就是答案了(即 > 0 >0 >0的位置)
2. 区间 + 1 +1 +1操作就要差分来维护,你要线段树也可以,YBH线段树
代码实现:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
int t;
int n,a[1000100],b[1000100],c[1000100];
int main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
read(t);
while (t--)
{
read(n);
for (int i=1;i<=n;i++)
read(a[i]),b[i]=0;
for (int i=1;i<=n;i++)
if (i<a[i])
b[i]+=1,b[a[i]+1]-=1;
int ans=0;
for (int i=1;i<=n;i++)
c[i]=c[i-1]+b[i];
for (int i=1;i<=n;i++)
if (c[i]>0) ans++;
printf("%d\n",ans);
}
// fclose(stdin);fclose(stdout);
return 0;
}
「EZEC-10」Shape(题目)
题目大意:如图所示
分析:
- 为了方便,我将白色点设为 1 1 1,黑色点设为 0 0 0
- 在设一个数组 l r [ i ] [ j ] lr[i][j] lr[i][j]表示第 i i i行第 j j j个想左边最多有多少个连续的白色(包括自己),在设一个数组 u d [ i ] [ j ] ud[i][j] ud[i][j]表示第 i i i行第 j j j个可以向上下同时延申多少格,这两个数组都可以用前缀和 O ( n ∗ m ) O(n*m) O(n∗m)维护
- 通过 l r [ i ] [ j ] lr[i][j] lr[i][j]我们可以知道每条横着的连续白点的位置,在通过 u d [ i ] [ j ] ud[i][j] ud[i][j]我们就可以得到这样一幅图这就是这些白点的一个连通块,将它们排序按 u p up up,并不会影响它们之间组成 H H H形的数量,对于 u p [ i ] [ j ] up[i][j] up[i][j]的来说他能与每一个 u p [ k ] [ l ] up[k][l] up[k][l]大于它的构成 u p [ i ] [ j ] up[i][j] up[i][j]个,因为与顺序无关,所以只用算比他大的个数乘以 u p [ i ] [ j ] up[i][j] up[i][j]就可以了,时间复杂度是 O ( n ∗ m ∗ log 2 m ) O(n*m*\log_2m) O(n∗m∗log2m)
代码实现:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
long long ans=0;
int n,m;
int mid_up_max[2*1010][2*1010],mid_down_max[2*1010][2*1010];
int up_down_min[2*1010][2*1010];
int left_right_max[2*1010][2*1010],up_down_min_sum[2*1010][2*1010];
int a[2*1010][2*1010];
int main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
read(n),read(m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
read(a[i][j]),a[i][j]=!a[i][j];
for (int i=1;i<=n;i++)
{
left_right_max[i][m+1]=-1;
for (int j=m;j>=1;j--)
if (a[i][j]==1)
left_right_max[i][j]=left_right_max[i][j+1]+1;
else
left_right_max[i][j]=-1;
}
for (int j=1;j<=m;j++)
{
mid_up_max[0][j]=-1;
for (int i=1;i<=n;i++)
if (a[i][j]==1)
mid_up_max[i][j]=mid_up_max[i-1][j]+1;
else
mid_up_max[i][j]=-1;
}
for (int j=1;j<=m;j++)
{
mid_down_max[n+1][j]=-1;
for (int i=n;i>=1;i--)
if (a[i][j]==1)
mid_down_max[i][j]=mid_down_max[i+1][j]+1;
else
mid_down_max[i][j]=-1;
}
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
up_down_min[i][j]=min(mid_up_max[i][j],mid_down_max[i][j]);
/*
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
printf("%d ",left_right_max[i][j]);
printf("\n");
}
printf("\n");
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
printf("%d ",up_down_min[i][j]);
printf("\n");
}
printf("\n");
*/
/*
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (up_down_min[i][j]==-1)
up_down_min_sum[i][j]=-1;
else if (up_down_min[i][j-1]==-1)
up_down_min_sum[i][j]=up_down_min[i][j];
else up_down_min_sum[i][j]=up_down_min_sum[i][j-1]+up_down_min[i][j];
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
printf("%d ",up_down_min_sum[i][j]);
printf("\n");
}
printf("\n");
*/
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (a[i][j]==1)
{
int s[2*1010],len=0;
for (int k=j;k<=j+left_right_max[i][j];k++)
s[++len]=up_down_min[i][k];
sort(s+1,s+len+1);
for (int k=1;k<=len;k++)
ans+=(len-k)*s[k];
j=j+left_right_max[i][j];
}
printf("%lld\n",ans);
// fclose(stdin);fclose(stdout);
return 0;
}
「EZEC-10」Covering(题目)
题目大意:有 k k k张长度为 1 ∗ 2 1*2 1∗2的纸片,从中选出 l l l~ r r r个,按从编号小到编号大的顺序放入 n ∗ m n*m n∗m的方格中,后放入的会覆盖前面的,使得最后的方格如给出的 n ∗ m n*m n∗m的方格一样,问有几种放法
分析:
- 分三步走,令 a n s = 1 ans=1 ans=1
- 先将编号在棋盘中出现两次的方格去掉,它们不会影响答案
- 找出出现过一次的纸片,对于每一张,它都会有上下左右编号大于它的纸片的总和的放法, a n s ∗ 上 下 左 右 大 于 它 的 总 和 数 ans*上下左右大于它的总和数 ans∗上下左右大于它的总和数
- 对于没有出现过的,我们设 f [ i ] [ j ] f[i][j] f[i][j]表示 1 1 1~ i i i张纸条中,选出 j j j个的放置方案总数,则我们要求的就是 f [ 没 有 出 现 过 的 总 数 ] [ m a x ( 0 , l − 出 现 过 的 总 数 ) ] + . . . + f [ 没 有 出 现 过 的 总 数 ] [ r − 出 现 过 的 总 数 ] f[没有出现过的总数][max(0,l-出现过的总数)]+...+f[没有出现过的总数][r-出现过的总数] f[没有出现过的总数][max(0,l−出现过的总数)]+...+f[没有出现过的总数][r−出现过的总数],最后将它乘上 a n s ans ans就好了,这个东西可以用 D P DP DP来求,转移式 f [ i ] [ j ] = i 的 放 法 ∗ f [ i − 1 ] [ j ] + f [ i ] [ j − 1 ] f[i][j]=i的放法*f[i-1][j]+f[i][j-1] f[i][j]=i的放法∗f[i−1][j]+f[i][j−1]
代码实现:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
#define mod 1000000007
using namespace std;
typedef long long ll;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
int t;
int n,m,k,l,r;
struct point { int cnt,x[2],y[2]; }p[1010];
int v[1010][1010],vl,a[1010][1010];
int f[1010][1010];
int num[1010],ans,len,bis;
int fx[4]={1,0,-1,0};
int fy[4]={0,1,0,-1};
int calc(int x,int y)
{
int g=0;
for (int t=0;t<=3;t++)
{
int tx=x+fx[t],ty=y+fy[t];
if (a[tx][ty]>a[x][y]) g++;
}
return g;
}
void down(int x,int y)
{
for (int t=0;t<=3;t++)
{
int tx=x+fx[t],ty=y+fy[t];
vl+=v[tx][ty];vl%=mod;
}
v[x][y]=1;
}
int max(int x,int y) { if (x>y) return x; else return y; }
signed main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
read(t);
while (t--)
{
memset(f,0,sizeof(f));
memset(num,0,sizeof(num));ans=len=0;bis=1;
memset(p,0,sizeof(p));memset(v,0,sizeof(v));
memset(a,0,sizeof(a));
read(n),read(m),read(k);read(l),read(r);vl=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
read(a[i][j]);
if (a[i][j]!=0)
p[a[i][j]].x[p[a[i][j]].cnt]=i,p[a[i][j]].y[p[a[i][j]].cnt]=j,p[a[i][j]].cnt++;
}
for (int i=k;i>=1;i--)
{
if (p[i].cnt==0) num[++len]=vl%mod;
else if (p[i].cnt==1) bis=bis*calc(p[i].x[0],p[i].y[0])%mod,down(p[i].x[0],p[i].y[0]);
else down(p[i].x[0],p[i].y[0]),down(p[i].x[1],p[i].y[1]);
}
f[len][0]=1;
for (int i=1;i<=len;i++) f[i][1]=(f[i-1][1]+num[i])%mod;
for (int i=2;i<=len;i++)
for (int j=2;j<=i;j++)
f[i][j]=((f[i-1][j-1]*num[i]%mod)+f[i-1][j])%mod;
// printf("%lld %lld %lld\n",l,k,len);
for (int i=max(0,l-k+len);i<=r-k+len;i++)
ans=(f[len][i]+ans)%mod;
// printf("%lld %lld\n",ans,bis);
printf("%lld\n",bis*ans%mod);
}
// fclose(stdin);fclose(stdout);
return 0;
}
「EZEC-10」序列(题目)
题目大意:如题
分析:
- 根据给出的关系式,将每个有关联的连一条边,边权为 z i z_i zi
2, 这样我们就得到了许多个联通块,每个连通块之间互补干扰,我们单独讨论一个连通块- 我们可以从一个点出发,遍历到每一个点,根据异或的结合律,我们就可以用起点表示出连通块中的每一个点,如果一个点有不同的表达方式,则无解
- 这样我们就要找到一个值,令起点为这个值,使得连通块上的每一点都小于 k k k,其实问的就是一个异或的最大值,容易想到用 t r i e trie trie树来维护
- 假设这个连通块的点为 a 1 , a 2 . . a n a_1,a_2..a_n a1,a2..an,令 a 2 = a 1 ⊗ g 1 , . . . a n = a 1 ⊗ g n a_2=a_1 \otimes g_1,...a_n=a_1 \otimes g_n a2=a1⊗g1,...an=a1⊗gn,将 g 1 . . g n g_1..g_n g1..gn插入树中,然后从根节点开始遍历, x , d , s u m x,d,sum x,d,sum表示当前节点的编号, d d d表示当前在第几位, s u m sum sum表示当前的异或值
- ①如果 x x x没有儿子,证明已经匹配完了,如果此时的 s u m ⩽ k sum \leqslant k sum⩽k,说明是可行的返回 1 1 1,否则返回 0 0 0
②如果有两个儿子,那无论这一位是什么,都可以异或到 1 1 1,所以 s u m + 2 d sum+2^d sum+2d, d f s 01 dfs01 dfs01儿子
③如果有一个儿子,我们设它为 0 0 0儿子,( 1 1 1儿子同理)如果 s u m + 2 d ⩽ k sum+2^d\leqslant k sum+2d⩽k说明这一位取 0 0 0 时,异或值为 0 0 0,肯定都是可以的,可以返回的答案 + 2 d +2^d +2d,再将 s u m + 2 d sum+2^d sum+2d, d f s dfs dfs 1 1 1儿子- 最后所有返回值相乘就是答案
代码实现:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define mod 1000000007
#define int long long
using namespace std;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
struct trie
{
int ch[31*5*100010][2];
int tot;
void init()
{
for (int i=1;i<=tot;i++) ch[i][0]=ch[i][1]=0;
tot=1;
}
void add(int x)
{
int now=1;
for (int i=30;i>=0;i--)
{
int t=(x>>i)&1;//printf("%d %d\n",now,t);
if (ch[now][t]==0)
{
tot++;
ch[now][t]=tot;
}
now=ch[now][t];
}
}
}tr;
int n,m,k,ans=1;
struct node { int i,j,x; }p[5*100010];
struct edge { int x,y,next,c; }a[5*2*100010];
int len,last[5*100010];
int bz[5*100010],re,vis[5*100010];
void ins(int x,int y,int c)
{
len++,a[len].x=x,a[len].y=y,a[len].c=c;
a[len].next=last[x],last[x]=len;
}
void wwww(int x,int fa)
{
if (re==1) return ;
tr.add(bz[x]);
vis[x]=1;
for (int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if (y!=fa)
{
if (bz[y]!=-1 && (bz[x]^a[k].c)!=bz[y]) { re=1; return ; }
bz[y]=bz[x]^a[k].c;
if (vis[y]==-1) wwww(y,x);
}
}
}
int dfs(int x,int d,int sum)
{
if (sum>k) return 0;
if (tr.ch[x][0]==0 && tr.ch[x][1]==0) return sum<=k;
if (tr.ch[x][0]>0 && tr.ch[x][1]>0)
{
sum+=(1<<d);
return dfs(tr.ch[x][0],d-1,sum)+dfs(tr.ch[x][1],d-1,sum);
}
if (tr.ch[x][0]>0)
{
if (sum+(1<<d)<=k) return (1<<d)+dfs(tr.ch[x][0],d-1,sum+(1<<d));
else return dfs(tr.ch[x][0],d-1,sum);
}
if (tr.ch[x][1]>0)
{
if (sum+(1<<d)<=k) return (1<<d)+dfs(tr.ch[x][1],d-1,sum+(1<<d));
else return dfs(tr.ch[x][1],d-1,sum);
}
}
signed main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
read(n),read(m),read(k);
for (int i=1;i<=n;i++) bz[i]=-1,vis[i]=-1;
for (int i=1;i<=m;i++)
{
read(p[i].i),read(p[i].j),read(p[i].x);
ins(p[i].i,p[i].j,p[i].x),ins(p[i].j,p[i].i,p[i].x);
}
for (int i=1;i<=n;i++)
{
if (bz[i]==-1)
{
if (last[i]==0) ans=ans*(k+1)%mod;
else
{
tr.init();
re=0;
bz[i]=0,wwww(i,-1);
if (re==1) { printf("0"); return 0; }
ans=ans*dfs(1,30,0)%mod;
}
}
}
printf("%lld\n",ans);
// fclose(stdin);fclose(stdout);
return 0;
}