前言
比赛链接:https://atcoder.jp/contests/arc107
止步 E E E题,完全不会。。。
上飞分啦!!!!
一下子从0变到900+,一场就绿啦,哈哈哈(雾
只可惜今天的CF比赛和ATcoder的比赛重时间了,还是先以codeforces为主吧。
A
题意: 给你 A , B , C A,B,C A,B,C,让你计算 ∑ a = 1 A ∑ b = 1 B ∑ c = 1 C a b c m o d 998244353 \sum\limits_{a=1}^{A}\sum\limits_{b=1}^{B}\sum\limits_{c=1}^{C}abc\mod{998244353} a=1∑Ab=1∑Bc=1∑Cabcmod998244353。
做法:首先根据乘法分配律,不难化成这个式子: A ( A + 1 ) 2 B ( B + 1 ) 2 B ( B + 1 ) 2 \frac{A(A+1)}{2}\frac{B(B+1)}{2}\frac{B(B+1)}{2} 2A(A+1)2B(B+1)2B(B+1),然后直接计算即可。
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const LL mod=998244353;
LL a,b,c;
int main()
{
scanf("%lld%lld%lld",&a,&b,&c);
a=(a+1)*a/2%mod;
b=(b+1)*b/2%mod;
c=(c+1)*c/2%mod;
printf("%lld\n",a*b%mod*c%mod);
return 0;
}
B
题意:给你
N
,
K
N,K
N,K,让你算满足要求的四元组个数。
对于四元组
(
a
,
b
,
c
,
d
)
(a,b,c,d)
(a,b,c,d),其需要满足:
1
≤
a
,
b
,
c
,
d
≤
N
,
a
+
b
−
c
−
d
=
K
1≤a,b,c,d≤N,a+b-c-d=K
1≤a,b,c,d≤N,a+b−c−d=K。
做法:
O
(
n
)
O(n)
O(n)可以过,不妨考虑
O
(
n
)
O(n)
O(n)的做法。
首先,我们可以再
O
(
1
)
O(1)
O(1)的时间在算出满足
1
≤
a
,
b
≤
N
,
a
+
b
=
t
1≤a,b≤N,a+b=t
1≤a,b≤N,a+b=t的二元组
(
a
,
b
)
(a,b)
(a,b)的个数,考虑化化式子:
(
a
+
b
)
−
(
c
+
d
)
=
K
(a+b)-(c+d)=K
(a+b)−(c+d)=K,那么只需要枚举
a
+
b
=
t
a+b=t
a+b=t中的
t
t
t,然后两边计算方案相乘即可,而且通过计算过程不难看出,答案是在
n
3
n^3
n3级别的,所以不会爆
l
o
n
g
long
long
l
o
n
g
long
long。
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
inline LL mymin(LL x,LL y){return x<y?x:y;}
inline LL mymax(LL x,LL y){return x>y?x:y;}
LL ans,n,k;
inline LL fangan(LL x)
{
LL l=mymax(1,x-n),r=mymin(n,x-1);
return r-l+1;
}
int main()
{
scanf("%lld%lld",&n,&k);
if(k<0)k=-k;
LL ed=n<<1;
for(LL i=k+2;i<=ed;i++)ans+=fangan(i)*fangan(i-k);
printf("%lld\n",ans);
return 0;
}
C
题意:对于给定的
n
∗
n
n*n
n∗n的矩阵,满足
a
i
,
j
a_{i,j}
ai,j各不相同且在
[
1
,
n
2
]
[1,n^2]
[1,n2]范围内。
现在给定一个
K
K
K,你可以进行无限次操作,每次操作有两个选择:
- 选定 x , y ( x ≠ y ) x,y(x≠y) x,y(x=y)行,如果 a x , i + a y , i ≤ K ( 1 ≤ i ≤ n ) a_{x,i}+a_{y,i}≤K(1≤i≤n) ax,i+ay,i≤K(1≤i≤n),那么交换这两行。
- 选定
x
,
y
(
x
≠
y
)
x,y(x≠y)
x,y(x=y)列,如果
a
i
,
x
+
a
i
,
y
≤
K
(
1
≤
i
≤
n
)
a_{i,x}+a_{i,y}≤K(1≤i≤n)
ai,x+ai,y≤K(1≤i≤n),那么交换这两列。
问你通过操作最多可以得到多少个不同的矩阵,对 998244353 998244353 998244353取模?
做法:不难发现,如果把每一行单独看成一个集合,那么不管进行哪个操作,这些集合都不会有任何改变,改变的只会是这些集合到底在哪一行,对列也是如此。
我们把行的编号列出一个数组 a a a, a a a数组初始就为 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n,每交换一次行,比如交换行 x , y x,y x,y,那么交换 a x , a y a_{x},a_{y} ax,ay,设 b a i = i b_{a_{i}}=i bai=i( b b b数组表示每个行在 a a a数组中的位置),根据上面的发现,不难推敲出:对于初始的 a a a数组, i i i行和 j j j行能交换,那么不管任何时候, a b i a_{b_{i}} abi和 a b j a_{b_{j}} abj都能进行交换(相应地, b i , b j b_{i},b_{j} bi,bj也会进行交换)。
那么发现了上述规律,如何统计方案呢?
我们把列的编号也列出一个数组 c c c,对应的便有 d d d数组,不难发现,初始的矩阵以及 a , c a,c a,c数组就可以确定一种不同的矩阵,特别的,初始的 a , c a,c a,c数组其实就对应了初始的矩阵,这样,我们只需要分别的统计 a a a数组能有多少种可能乘以 c c c数组的便是答案(事实上, b , d b,d b,d数组更加的直观,后面直接讲 b , d b,d b,d数组)。
那么如何求 b b b数组有多少种呢?我们不妨建一个 n n n个点的图, i , j i,j i,j连边表示在初始矩阵中第 i , j i,j i,j行能够交换,现在证明,对于一个联通块内的两个点,其可以自由♂的交换。
考虑联通块中的一条链: x − t 1 − t 2 − . . . − t k − y x-t_1-t_2-...-t_k-y x−t1−t2−...−tk−y,只要我们能证明能够在不改变 b t i b_{t_{i}} bti的情况下,让 b x b_{x} bx和 b y b_{y} by交换即可,考虑数学归纳法,当 k = 1 k=1 k=1时, x , t 1 x,t_1 x,t1交换,然后然后 t 1 t_1 t1和 y y y交换, x x x再和 t 1 t_{1} t1交换便可以了,对于 k > 1 k>1 k>1的情况,我们只需要交换 x , t k x,t_{k} x,tk,然后再交换 t k , y t_{k},y tk,y,最后再交换 x , t k x,t_{k} x,tk即可,证毕,因此,一个联通块中的点可以互相交换,所以一个联通块最多会有 联 通 块 大 小 ! 联通块大小! 联通块大小!个不同的方案, d d d数组也是同理。
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)(因为用了并查集)
#include<cstdio>
#include<cstring>
#define N 60
using namespace std;
typedef long long LL;
const LL mod=998244353;
int a[N][N],n,k;
inline bool check1(int x,int y)//行比较
{
for(int i=1;i<=n;i++)
{
if(a[x][i]+a[y][i]>k)return 0;
}
return 1;
}
inline bool check2(int x,int y)//行比较
{
for(int i=1;i<=n;i++)
{
if(a[i][x]+a[i][y]>k)return 0;
}
return 1;
}
LL fc[N];
int fa[N],siz[N];
int findfa(int x)
{
if(fa[x]!=x)fa[x]=findfa(fa[x]);
return fa[x];
}
inline void mer(int x,int y)
{
int tx=findfa(x),ty=findfa(y);
if(tx!=ty)
{
fa[tx]=ty;
siz[ty]+=siz[tx];
}
}
int main()
{
scanf("%d%d",&n,&k);
fc[1]=1;for(int i=2;i<=n;i++)fc[i]=(fc[i-1]*i)%mod;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
fa[i]=i;
siz[i]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(findfa(i)!=findfa(j) && check1(i,j)==1)mer(i,j);
}
}
LL ans=1;
for(int i=1;i<=n;i++)
{
if(findfa(i)==i)ans=ans*fc[siz[i]]%mod;
}
for(int i=1;i<=n;i++)fa[i]=i,siz[i]=1;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(findfa(i)!=findfa(j) && check2(i,j)==1)mer(i,j);
}
}
for(int i=1;i<=n;i++)
{
if(findfa(i)==i)ans=ans*fc[siz[i]]%mod;
}
printf("%lld\n",ans);
return 0;
}
D
题意:对于给定的正整数 N , K N,K N,K,求有多少个集满足一下要求:
- 集中的数字个数是 n n n个。
- 集中的每个数字都能被表示为: 1 2 t ( t ≥ 0 ) \frac{1}{2^t}(t≥0) 2t1(t≥0)
- 集中每个数字的和是
K
K
K。
需要注意的是,集是无序的,即 1 , 1 2 1,\frac{1}{2} 1,21和 1 2 , 1 \frac{1}{2},1 21,1是等价的。
做法:第一眼:肯定不是DP。
还真是DP,甚至当时都没发现自己已经想到做法了
首先,不难想的是,默认集中的数字从大到小排序,这样比较好处理,但是怎么枚举方案呢?
不妨认为一开始有
K
K
K个
1
1
1,然后枚举有多少个
1
1
1分成了
1
2
\frac{1}{2}
21,设有
i
i
i个
a
1
a_1
a1分裂了吧,这样我们就有
2
a
1
2a_1
2a1个
1
2
\frac{1}{2}
21,然后我们可以继续枚举分裂多少个
1
2
\frac{1}{2}
21变成
1
4
\frac{1}{4}
41,记为
a
2
a_{2}
a2。(事实上,不用考虑
1
1
1直接分裂成四个
1
4
\frac{1}{4}
41的情况,因为其必须先分裂成
1
2
\frac{1}{2}
21)
不断的枚举下去,我们可以得到 a a a序列: a 1 , a 2 , a 3 , a 4 , . . . , a k a_1,a_2,a_3,a_4,...,a_k a1,a2,a3,a4,...,ak,不难发现: a 1 ≤ K , a i ≤ 2 a i − 1 ( i > 1 ) a_{1}≤K,a_{i}≤2a_{i-1}(i>1) a1≤K,ai≤2ai−1(i>1),同时因为每分裂一次多一个数字,所以 a 1 + a 2 + a 3 + . . . + a k = n − K a_1+a_2+a_3+...+a_k=n-K a1+a2+a3+...+ak=n−K,所以只需要统计不同的 a a a序列即可, D P DP DP完全可以 O ( n 2 ) O(n^2) O(n2)转移。
#include<cstdio>
#include<cstring>
#define N 3100
using namespace std;
const int mod=998244353;
int f[N][N];//统计方案
int n,m;
int main()
{
scanf("%d%d",&n,&m);
if(n==m)
{
printf("1\n");
return 0;
}
int limit=n-m;//背包数量
for(int i=1;i<=limit;i++)
{
if(i<=m)f[i][i]=1;//a1
for(int j=1;j<i;j++)f[i][j]=(f[i][j]+f[i-j][(j+1)/2])%mod;
for(int j=i;j>=1;j--)f[i][j]=(f[i][j]+f[i][j+1])%mod;
}
printf("%d\n",f[limit][1]);
return 0;
}
E
题意:对于
n
∗
n
n*n
n∗n的矩阵,给了你第一行和第一列,对于
a
i
,
j
=
m
e
x
(
a
i
−
1
,
j
,
a
i
,
j
−
1
)
(
i
>
1
,
j
>
1
)
a_{i,j}=mex(a_{i-1,j},a_{i,j-1})(i>1,j>1)
ai,j=mex(ai−1,j,ai,j−1)(i>1,j>1),矩阵满足:
0
≤
a
i
,
j
<
3
(
∀
i
∈
[
1
,
n
]
,
j
∈
[
1
,
n
]
)
0≤a_{i,j}<3(∀i∈[1,n],j∈[1,n])
0≤ai,j<3(∀i∈[1,n],j∈[1,n]),其中
m
e
x
mex
mex是其自己定义的运算。
做法:神TM找规律???我一个常年走路想题的,你跟我说要暴力找规律???
给一个官方给出的
n
=
20
n=20
n=20的随机矩阵:
不难发现,有很多位置
a
i
,
j
=
a
i
−
1
,
j
−
1
a_{i,j}=a_{i-1,j-1}
ai,j=ai−1,j−1,事实上,你的感觉没错,只要
i
>
4
,
j
>
4
i>4,j>4
i>4,j>4就满足这个性质,然后只要暴力枚举前四行四列即可。
至于证明,据说是暴力跑 n = 5 n=5 n=5的矩阵,枚举所有的情况,可以发现 a 4 , 4 = a 5 , 5 a_{4,4}=a_{5,5} a4,4=a5,5,然后只要对于每一个 i > 4 , j > 4 i>4,j>4 i>4,j>4的位置,就可以通过这个性质判定 a i − 1 , j − 1 = a i , j a_{i-1,j-1}=a_{i,j} ai−1,j−1=ai,j了。
时间复杂度: O ( n ) O(n) O(n)
#include<cstdio>
#include<cstring>
#define N 510000
using namespace std;
typedef long long LL;
int a[6][N],b[N][6];
int n;
LL cnt[5];
inline int mex(int x,int y)
{
if(x>y)x^=y^=x^=y;//x<=y
return (x>0)?0:((y&1)?2:1);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[1][i]);
b[1][1]=a[1][1];
for(int i=2;i<=n;i++)scanf("%d",&b[i][1]);
if(n<=4)
{
for(int i=2;i<=n;i++)a[i][1]=b[i][1];
for(int i=2;i<=n;i++)
{
for(int j=2;j<=n;j++)a[i][j]=mex(a[i][j-1],a[i-1][j]);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)cnt[a[i][j]]++;
}
printf("%lld %lld %lld\n",cnt[0],cnt[1],cnt[2]);
return 0;
}
a[2][1]=b[2][1];a[3][1]=b[3][1];a[4][1]=b[4][1];
b[1][2]=a[1][2];b[1][3]=a[1][3];b[1][4]=a[1][4];
for(int i=2;i<=4;i++)
{
for(int j=2;j<=n;j++)a[i][j]=mex(a[i][j-1],a[i-1][j]);
}
for(int i=2;i<=4;i++)
{
for(int j=2;j<=n;j++)b[j][i]=mex(b[j-1][i],b[j][i-1]);
}
for(int i=1;i<=4;i++)
{
for(int j=1;j<=n;j++)cnt[a[i][j]]++;
}
for(int i=1;i<=4;i++)
{
for(int j=5;j<=n;j++)cnt[b[j][i]]++;
}
for(int i=4;i<=n;i++)cnt[a[4][i]]+=n-i;
for(int i=5;i<=n;i++)cnt[b[i][4]]+=n-i;
printf("%lld %lld %lld\n",cnt[0],cnt[1],cnt[2]);
return 0;
}
F
下文有借鉴或者直接搬运https://www.cnblogs.com/gmh77/p/13908571.html博客的内容。
题意:给出一个无向图,可以删掉若干点,删
i
i
i的代价是
a
i
a_i
ai,一个联通块的价值为其中每个点的
b
i
b_i
bi之和的绝对值,最大化
Σ
新
图
中
每
个
连
通
块
的
价
值
−
删
点
代
价
Σ新图中每个连通块的价值-删点代价
Σ新图中每个连通块的价值−删点代价。
1
≤
n
,
m
<
=
300
1≤n,m<=300
1≤n,m<=300
题解:先删掉一些点,对剩下的一个块里的贡献同为 + 1 +1 +1或 − 1 -1 −1,则可以转化为对每个点赋+1/-1/删掉,最终贡献为bi*点权之和,且有边相连的点的权相同
先加上 Σ ∣ b i ∣ Σ|bi| Σ∣bi∣,接下来用最小割减去使其合法的最小代价即可。
连S->i1->i2->T,对应+1/删/-1,若bi>=0则为0/bi+ai/2bi,<0则为2|bi|/|bi|+ai/0
对于一条边,其两端的点编号不能不同,所以对于(u,v)直接连v2->u1和u2->v1的inf边即可。
不难发现,这样子构造边权都是非负的,直接最小割走起。
时间复杂度: O ( n 2 ( n + m ) ) O(n^2(n+m)) O(n2(n+m))
#include<cstdio>
#include<cstring>
#define N 610
#define M 3100
using namespace std;
typedef long long LL;
template<class T>
inline T mymin(T x,T y){return x<y?x:y;}
struct node
{
int y,next;
LL c;
}a[M];int len=1,last[N];
inline void insnode(int x,int y,LL c){len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
inline void ins(int x,int y,LL c){insnode(x,y,c);insnode(y,x,0);}
int h[N],list[N],head,tail,st,ed;
bool bfs()
{
memset(h,0,sizeof(h));h[ed]=1;
list[head=tail=1]=ed;
while(head<=tail)
{
int x=list[head++];
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if(!h[y] && a[k^1].c)h[y]=h[x]+1,list[++tail]=y;
}
}
return h[st];
}
LL findflow(int x,LL f)
{
if(x==ed)return f;
LL s=0,t;
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if(h[y]+1==h[x] && a[k].c)
{
s+=t=findflow(y,mymin(f-s,a[k].c));
a[k].c-=t;a[k^1].c+=t;
if(s==f)return s;
}
}
if(!s)h[x]=0;
return s;
}
int n,m;
LL ans=0,aa[N],bb[N];
int main()
{
scanf("%d%d",&n,&m);st=(n<<1)+1;ed=(n<<1)+2;
for(int i=1;i<=n;i++)scanf("%lld",&aa[i]);
for(int i=1;i<=n;i++)scanf("%lld",&bb[i]);
for(int i=1;i<=n;i++)
{
if(bb[i]>=0)
{
ans+=bb[i];
ins(i,i+n,aa[i]+bb[i]);
ins(i+n,ed,bb[i]<<1);
}
else
{
ans+=-bb[i];
ins(st,i,(-bb[i])<<1);
ins(i,i+n,aa[i]+(-bb[i]));
}
}
for(int i=1;i<=m;i++)
{
int x,y;scanf("%d%d",&x,&y);
ins(x+n,y,(LL)999999999999999);
ins(y+n,x,(LL)999999999999999);
}
while(bfs()==1)
{
ans-=findflow(st,(LL)999999999999999);
}
printf("%lld\n",ans);
return 0;
}
小结
还不够强,后面两道题还做不出来。
找规律太草了