前排提示: D D D题大力分类讨论题, E E E题入门状压题, F F F题 L I S LIS LIS板子题
A . A. A.
题意:
c o d e : code: code:
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#define int long long
using namespace std;
int n,m,h,w;
signed main()
{
scanf("%lld",&h);
int i=1;int tot=1;
while(1)
{
if(tot>h)
{
printf("%lld\n",i);
break;
}
tot+=(1<<i);
i++;
}
return 0;
}
B . B. B.
题意:一共 N N N个人,每个人都有一个 S i S_i Si的字符串表示名字和一个 C i C_i Ci表示分数,把 N N N个人按名字的字典序编号为 0 , 1 , 2... N − 1 0,1,2...N-1 0,1,2...N−1,求第 T m o d N T mod N TmodN个人的名字,其中 T T T表示 Σ C i \Sigma C_i ΣCi
数据规模: N < = 100 , C i < = 4229 , l e n t h ( S i ) < = 16 N<=100,C_i <= 4229,lenth(S_i)<=16 N<=100,Ci<=4229,lenth(Si)<=16
c o d e : code: code:
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
const int N = 105;
int t;
string s[N];
int a[N];
int sum;
int n,m,h,w;
bool cmp(string a,string b)
{
return a<b;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
cin>>s[i];
scanf("%d",&a[i]);
sum+=a[i];
}
int winn=(sum%n);
sort(s,s+n,cmp);
cout<<s[winn];
return 0;
}
C . C. C.
题意: N N N张牌编号 1 , 2... N 1,2...N 1,2...N,每张牌有强度 A i A_i Ai和成本 C i C_i Ci,要删去所有 A x < A y A_x<A_y Ax<Ay且 C x > C y C_x>C_y Cx>Cy的卡牌,输出剩下卡片的编号
数据规模: N < = 2 ∗ 1 0 5 N<=2*10^5 N<=2∗105
做法:排序。我们先按 A i A_i Ai从小到大排序,对于第 i i i张牌来讲,它的强度 A i A_i Ai比后边每一张都小。那么如果后边有一张牌 j j j,他的成本 C j C_j Cj比第 i i i张牌 C i C_i Ci的小,我们就要把第 i i i张牌删去。可以发现,这实际上是维护后缀最小值。详细地说,我们从后往前维护一个 C C C的最小值,如果当前这张 C i C_i Ci比最小值大,我们就删去这张,如果当前这张 C i C_i Ci比最小值小,我们就保留并把最小值更新为 C i C_i Ci
c o d e : code: code:
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
const int N = 2e5;
int t;
struct node
{
int aa,bb,pl;
};
node c[N];
int anss[N];
bool vis[N];
int head;
bool cmp(node a,node b)
{
return a.aa < b.aa;
}
int n,m,h,w;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&c[i].aa,&c[i].bb);
c[i].pl=i;
}
sort(c+1,c+1+n,cmp);
int minn=c[n].bb;
int ans=n;
for(int i=n;i>=1;i--)
{
if(c[i].bb>minn)
{
ans--;
vis[c[i].pl]=1;
}
else
{
minn=c[i].bb;
}
}
printf("%d\n",ans);
for(int i=1;i<=n;i++)
{
if(!vis[i]) printf("%d ",i);
}
return 0;
}
D . D. D.
题意:
给定某个矩阵左下角坐标
(
a
,
b
)
(a,b)
(a,b)和右上角坐标
(
c
,
d
)
(c,d)
(c,d),求:
矩阵覆盖的阴影部分面积
∗
2
*2
∗2。
每个阴影三角形的边长为1。
做法:
狗屎题 需要大力讨论。先找循环节,发现最小循环节长这个样子
长为4,宽为2。
最小循环节阴影部分为8(答案要×2,所以我们讨论的所有阴影面积就直接×2了)
首先,考虑左下角坐标为原点的矩形怎么算阴影面积。形如这样,红色就是要求的矩形面积,蓝色是最多的循环节,绿色和紫色是剩余部分。
S
蓝
=
⌊
x
/
4
⌋
∗
⌊
y
/
2
⌋
∗
8
S_蓝 = \lfloor x /4\rfloor*\lfloor y/2\rfloor*8
S蓝=⌊x/4⌋∗⌊y/2⌋∗8
S
绿
S_绿
S绿需要按长为1,2,3三种情况分类,每种情况又要考虑宽是奇数还是偶数
S
紫
S_紫
S紫只有
y
y
y是奇数时才存在,此时
S
紫
=
⌊
x
/
4
⌋
∗
6
S_紫 = \lfloor x /4\rfloor*6
S紫=⌊x/4⌋∗6
然后我们任取一个在第一象限的矩形,发现它可以转化成好几个左下角为原点的矩形拼凑出来,如图所示,
S
黑
=
S
红
−
S
蓝
−
S
紫
+
S
黄
S_黑 = S_红 - S_蓝 - S_紫 +S_黄
S黑=S红−S蓝−S紫+S黄
最后,对于任意的矩形,我们可以把它平移到第一象限,看数据规模,只需要把左下坐标和右上坐标都加上
1
0
9
10^9
109就可以,因为
1
0
9
10^9
109是
4
和
2
4和2
4和2的倍数,所以面积不变
数据规模; − 1 0 9 < = a , b , c , d < = 1 0 9 -10^9<=a,b,c,d<=10^9 −109<=a,b,c,d<=109
c o d e : code: code:
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#define int long long
using namespace std;
int n,m,h,w;
const int M = 1e9;
int f(int x,int y)
{
int ans=0;
ans+=(x/4)*(y/2)*8;
if(x%4==1)//宽为1的绿色
{
ans+=3*(y/2);
if(y%2) ans+=2;//多出来右上角一小块
}
if(x%4==2)//宽为2
{
ans+=3*y;//每一行的面积都是3,所以不用考虑y的奇偶
}
if(x%4==3)//宽为3
{
ans+=7*(y/2);
if(y%2) ans+=3;//多出来右上角一块
}
if(y%2) ans+=(x/4)*4;//紫色部分面积
return ans;
}
int a,b,c,d;
signed main()
{
scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
a+=M;b+=M;c+=M;d+=M;
printf("%lld\n",f(c,d)-f(a,d)-f(c,b)+f(a,b));
return 0;
}
//0 0 5 5
E . E. E.
题意:
数据规模: N < = 18 N<=18 N<=18
做法:状压+
d
p
dp
dp/记忆化搜索。设
F
[
x
]
F[x]
F[x]以
x
x
x的二进制01串为局面,
x
x
x的第
i
i
i位为1表示当前第
i
i
i张牌还在桌子上,为
0
0
0表示不在桌子上。若
F
[
x
]
=
=
1
F[x]==1
F[x]==1则当前局面先手必胜,
F
[
x
]
=
=
2
F[x]==2
F[x]==2则当前局面后手必胜,
F
[
x
]
=
=
0
F[x]==0
F[x]==0则当前局面还未被搜到。怎么算
F
[
x
]
F[x]
F[x]呢?我们枚举当前状态可以拿走哪两张牌达到下一状态
F
[
y
]
F[y]
F[y],如果所有的
F
[
y
]
F[y]
F[y]都是1,那么
F
[
x
]
=
2
F[x]=2
F[x]=2,否则
F
[
x
]
=
1
F[x]=1
F[x]=1。
直观理解一下这个转移方程,如果当前牌面任意拿走两张可以拿掉的牌形成新牌面,新牌面都是先手必胜,那么旧牌面一定是后手必胜的(旧牌面的后手和新牌面的先手是同一个人)。如果新牌面至少有一种情况是后手必胜,那么旧牌面一定是先手必胜的(旧牌面的先手一定会选择新牌面是后手必胜的情况去拿牌,这样可以让自己在新牌面必胜,旧牌面的先手和新牌面的后手是同一个人)。
如果没法翻牌了,那么当前局面就是后手胜,
F
=
2
F=2
F=2,这是动态规划的初始化或者搜索终止的局面。
答案就是
F
[
2
N
−
1
]
F[2^N-1]
F[2N−1]
c o d e : code: code:
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
const int N = 20;
int t;
int a[N],b[N];
int f[1<<N];
int n,m,h,w;
int dit(int x)
{
int ans=0;
while(x)
{
x>>=1;
ans++;
}
return ans;
}
int dfs(int x)
{
if(f[x]) return f[x];
int dits=dit(x);
// printf("x = %d dit = %d\n",x,dits);
for(int i=1;i<=dits-1;i++)
{
for(int j=i+1;j<=dits;j++)
{
if(((x>>(i-1))&1)&&((x>>(j-1))&1))
if(a[i]==a[j]||b[i]==b[j])
{
int y=x-(1<<(i-1))-(1<<(j-1));
dfs(x-(1<<(i-1))-(1<<(j-1)));
if(f[y]==2) f[x]=1;
}
}
}
if(f[x]!=1) f[x]=2;//无牌可拿或者所有新牌面都是先手必胜,则后手必胜,俩情况合一起写了
return f[x];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);
int ans=dfs((1<<n)-1);
if(ans==1) printf("Takahashi");
if(ans==2) printf("Aoki");
return 0;
}
F . F. F.
题意:给一个序列 A 1 , A 2 , . . . A N A_1,A_2,...A_N A1,A2,...AN,判断每个 A i A_i Ai是否出现在至少一个最长上升子序列 ( L I S ) (LIS) (LIS)中。
数据规模: N < = 2 ∗ 1 0 5 N<=2*10^5 N<=2∗105
做法:首先要知道最长上升子序列可以有很多条。不难发现,如果
以 A i 结尾的 L I S 长度 + 以 A i 开头的 L I S 长度 − 1 = L e n t h ( L I S ) 以A_i结尾的LIS长度 +以A_i开头的LIS长度 -1 = Lenth(LIS) 以Ai结尾的LIS长度+以Ai开头的LIS长度−1=Lenth(LIS)
那么 A i A_i Ai就在起码一个最长上升子序列中,这个 L I S LIS LIS就是等式左侧的两部分拼接起来。
前一个就是 L I S LIS LIS模板,后一个的长度就是把序列前后颠倒,然后求以 A i A_i Ai结尾的最长下降子序列长度。
代码就不放了,我还没写完