周赛Round31
D.map模拟静态数组
给一个数列,要完成两种操作:在一个指定元素的右侧插入,删除指定元素。 维护序列首先考虑一下最经典的两种结构:数组和链表,数组插入和删除都需要O(n),而本题操作次数有1e5,舍去。链表插入是只需要O(1),但是寻找插入位置需要O(n),也不行。
看起来都不行了,那怎么办呢?实际上这是我们的思路被这两种经典模型限制住了,除了数组和指针链表外,还有一种维护序列插入删除的结构:静态链表,对于每一个元素,直接记录他左右的元素,那么插入时,就可以O(1)找到插入位置,O(1)完成修改。不过本题元素大小有1e9,单纯用数组的话会MLE,可以用map进行离散化,代价是寻找插入位置从O(1)变成了O(logn),但也还能接受。
需要注意的是在最左侧插入删除需要特判,也可以先插入inf和-inf作为两侧元素,就不用特判了。
using namespace std;
map<int,int>l,r;
int main(){
int q,x,y,head=0,cnt=0;
cin>>q;
for(int i=1;i<=q;i++){
int op;
cin>>op;
if(op==1){
cnt++;
cin>>x>>y;
if(y==0){//最左侧插入
if(head){//如果不为空
l[head]=x;
r[x]=head;
head=x;
}
else head=x;//如果为空
}
else{//其他位置插入
r[x]=r[y];
l[x]=y;
l[r[y]]=x;
r[y]=x;
}
}
else{
cnt--;
cin>>x;
if(head==x){//删除最左侧
head=r[x];
}
else{//删除其他位置
r[l[x]]=r[x];
l[r[x]]=l[x];
}
}
}
cout<<cnt<<'\n';
for(int i=1;i<=cnt;i++){
cout<<head<<' ';
head=r[head];
}
}
还有另一种思路也是可行的,使用指针链表,然后用map<int,node*>记录每个元素所在节点的地址,也可以O(logn)找到操作元素,O(1)操作
E.背包dp变形
可以把数列中一些元素取反,问是否能使数列元素和为零,如果可以,求最小操作次数。
由于元素个数2e2,元素绝对值不超过2e2,进行任意操作,元素和绝对值都不会超过4e4,一共只有8e4种可能的取值,那么可以用一种相当暴力的办法:维护一个dp(i,j),记录对于前i个元素,使数列元素和为j,最少需要的操作次数,对于每一个i,都遍历8e4个可能取值进行转移。
具体来说,对于i,j,第i个元素为x,那么当前元素x可以选择保持初始符号,直接加进来,那么dp(i,j)从dp(i-1,j-x)转移过来,也可以选择取反再加,那么dp(i,j)从dp(i-1,j+x)+1转移过来,由于进行了取反操作,操作次数加一。
需要注意的是j+x,j-x需要判断是否越界;由于转移过程中要取min,要给dp数组赋初始值inf;并且对于(-4e4,4e4)的下标,由于数组下标只能取正数,需要把它们映射到(0,8e4)上,映射后的4e4对应原来的0。最后检查dp(n,4e4)是否为初始值(inf)判断能否使元素和为0。
using namespace std;
int dp[250][80050];
int main(){
int n;
cin>>n;
memset(dp,0x3f,sizeof(dp));//初始值赋inf
dp[0][40000]=0;//开始和为0,操作数为0
for(int i=1;i<=n;i++){
int x;
cin>>x;
for(int j=0;j<=80000;j++){
if(j-x>=0&&j-x<=80000)dp[i][j]=min(dp[i][j],dp[i-1][j-x]);
if(j+x>=0&&j+x<=80000)dp[i][j]=min(dp[i][j],dp[i-1][j+x]+1);
}
}
if(dp[n][40000]<n)cout<<dp[n][40000];//如果不为初始值,说明可能
else cout<<-1;//否则不可能
}
F.插板法
两种元素分别有x,y个,问段数为分别为[1,x+y]的摆放方式分别有几种。
假设段数为i,由于只有两种元素,只能两种元素交叉排列,如果i为偶,两种元素段数都为i/2,如果i为奇,那么开头元素有i/2+1段,另一个有i/2段。两种元素都可能在第一个,那么总的方案数就是x在第一个的方案数+y在第一个的方案数。
假设x在第一个,那么问题就转化为了求把x分为(i/2+i%2)段,y分为i/2段的方案数,很显然由于被分的元素都是无区别的,这就是一个插板法:把m个无区别的东西分成k份,等价于在m-1个空里选k-1个进行插板,总方案数为C(m-1,k-1)。
接下来就简单了,但需要注意的是用到阶乘最好先预处理,用的时候直接查表,并且求阶乘时可能会爆int,最好直接define int ll
;这种做法可能会出现需要分的段数比x,y还多的情况,不需要在主函数里进行特判,直接在C(m,k)里面对于k>m,k<0进行特判返回零就可以了。
using namespace std;
#define ll long long
const int mod=1e9+7;
const int N=1e3+10;
#define int ll
ll fac[N],ifac[N];
ll qpow(int a,int b){
ll ret=1;
while(b){
if(b&1)ret=ret*a%mod;
a=a*a%mod;
b>>=1;
}
return ret;
}
ll inv(int x){
return qpow(x,mod-2);
}
void init(int x){
fac[0]=1;
for(int i=1;i<=x;i++){
fac[i]=fac[i-1]*i%mod;
}
ifac[x]=inv(fac[x]);
for(int i=x-1;i>=0;i--){
ifac[i]=ifac[i+1]*(i+1)%mod;
}
}
ll C(int m,int k){
if(m<0||k<0||k>m)return 0;
return fac[m]*ifac[k]%mod*ifac[m-k]%mod;
}
signed main(){
int x,y;
cin>>x>>y;
init(1000);
for(int i=1;i<=x+y;i++){
int k1=i/2+i%2,k2=i/2;
cout<<(C(x-1,k1-1)*C(y-1,k2-1)%mod+C(y-1,k1-1)*C(x-1,k2-1)%mod)%mod<<'\n';
}
}