蒟蒻的补档题(长期更新)
补档
长期更新……这里是我做过并且感觉有收获的题
小仙女过生日啦
看了题解,是“区间dp经典例题——“凸多边形的三角剖分””……但是还没懂
知识点
1.叉积求三角形面积
之前自己只会个海伦公式……
找这个的时候我还看到了行列式,是线代里的,自己本来是打算寒假学的,结果净去过写题了……
double S(point a,point b,point c){
//注意这里相减的顺序不能变
return fabs((b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x))/2;
}
2.判断一个点是否在三角形内
bool judge(point a,point b,point c){
double s1=S(a,b,c);
rep(i,1,n){
int x=p[i].x,y=p[i].y;
if(x==a.x&&y==a.y||x==b.x&&y==b.y||x==c.x&&y==c.y){
continue;
}
double s2=S(a,b,p[i])+S(a,c,p[i])+S(b,c,p[i]);
//因为是浮点数,所以不能直接相等
if(fabs(s1-s2)<=1e-8) return false;
}
return true;
}
代码
蒟蒻只会dp的板子,这主函数部分就看不懂了……
const int N=1e2+5;
double f[N][N];
int n;
struct point{
double x,y;
}p[N];
double S(point a,point b,point c){
return fabs((b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x))/2;
}
bool judge(point a,point b,point c){
double s1=S(a,b,c);
rep(i,1,n){
int x=p[i].x,y=p[i].y;
if(x==a.x&&y==a.y||x==b.x&&y==b.y||x==c.x&&y==c.y){
continue;
}
double s2=S(a,b,p[i])+S(a,c,p[i])+S(b,c,p[i]);
if(fabs(s1-s2)<=1e-6) return false;
}
return true;
}
signed main()
{
//std::ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
while(cin>>n){
rep(i,1,n) cin>>p[i].x>>p[i].y;
rep(i,1,n-2){
f[i][i+2]=S(p[i],p[i+1],p[i+2]);
}
rep(len,4,n){
rep(i,1,n-len+1){
int j=i+len-1;
f[i][j]=0x3f3f3f3f;
rep(k,i+1,j-1){
if(judge(p[i],p[k],p[j])){
f[i][j]=min(f[i][j],max(S(p[i],p[k],p[j]),max(f[i][k],f[k][j])));
}
}
}
}
cout<<fixed<<setprecision(1)<<f[1][n]<<endl;
}
return 0;
}
Forsaken喜欢数论
求前n个数的最小质因子的和。
在从 1 到 n遍历整数时,使用筛法求素数。
如果本身是素数,那么它本身就是它的最小质因子,累加计入 ans。
遍历由该素数生成的合数,如果还没访问过,表示这个合数的最小质因子就是该素数,累加计入ans。——Forsaken喜欢数论_牛客博客
int n,ans=0;cin>>n;
vector<bool>v(n+1,0);
for(int i=2;i<=n;i++){
if(!v[i]){
ans+=i;
for(int j=i*i;j<=n;j+=i){
if(!v[j]){
v[j]=1;
ans+=i;
}
}
}
}
cout<<ans;
return 0;
字符串
双指针
先找到一个从0开始包含所有小写字母的最短长度,并统计每个小写出现多少次,然后右指针移位,相应的字符统计++,然后对与左指针进行移位,移位到不能移位为止,即两指针中间出现有些字符不存在为止 ——字符串_牛客博客
const int N=27;
int v[N];
signed main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
string s;cin>>s;
int sum=0,r=-1,ans=1e9,len=s.length();
for(int l=0;l<len;l++){
while(sum<26){
r++;
if(r>len-1) break;
if(!v[s[r]-'a']) sum++;
v[s[r]-'a']++;
}
if(v[s[l]-'a']==1) sum--;
v[s[l]-'a']--;
if(sum==26) ans=min(ans,r-l);
}
cout<<ans;
return 0;
}
小红勇闯地下城
最短路 迪杰斯特拉
因为读入权值的问题卡了两小时(⊙﹏⊙)
要注意的点
学了果然完全不等于会了-^-
const int N=1e5+5;
int dis[N],v[N];
vector<pair<int,int>>g[N];
string s[N];
int op[4][2]={1,0,-1,0,0,1,0,-1};
//这种写法:op[0][0]=1,op[0][1]=0,op[1][0]=-1,op[1][1]=0
signed main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int q;cin>>q;
while(q--){
int n,m,h;cin>>n>>m>>h;
//不能从s[1]开始输入,所以此次只能从0开始
rep(i,0,n-1) cin>>s[i];
int sx=0,sy=0,ex=0,ey=0;
rep(i,0,n-1){
rep(j,0,m-1){
dis[i*m+j]=1e9;
v[i*m+j]=0;
g[i*m+j].clear();
//当时这里给写下面一个循环去了
if(s[i][j]=='T') ex=i,ey=j,s[i][j]='0';
else if(s[i][j]=='S') sx=i,sy=j,s[i][j]='0';
}
}
rep(i,0,n-1){
rep(j,0,m-1){
rep(k,0,3){
//注意nx与i的对应
int nx=i+op[k][0],ny=j+op[k][1];
if(nx<0||ny<0||nx==n||ny==m) continue;
g[i*m+j].push_back({nx*m+ny,s[nx][ny]-'0'});
}
}
}
priority_queue<pair<int,int>>qq;
dis[sx*m+sy]=0;
qq.push({0,sx*m+sy});
//此题可以不用判重
while(!qq.empty()){
auto t=qq.top();
qq.pop();
int u=t.second;
//if(v[u]) continue;
//v[u]=1;
for(auto ed:g[u]){
int v=ed.first,w=ed.second;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
qq.push({-dis[u],v});
}
}
}
if(dis[ex*m+ey]<h) cout<<"Yes";
else cout<<"No";
cout<<endl;
}
return 0;
}
E-小红的树形 dp_牛周赛 Round 34
初看是图论(有建边),再看是数据结构(有建树),现在看是二者皆有
二分图染色,还是第一次见到这个知识点的题(⊙﹏⊙)
知识点
二分图染色 实际上是一种树的应用(个人理解),父节点(可以理解为一条边的一点)与其子节点(与该点相连的另一个点)颜色相反,为了实现这种操作,需要建树加染色两步
//建树,分别是u作父节点,v作子节点;u作子节点,v作父节点
rep(i,1,n-1){
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
//染色,先把第一个节点染色为1,枚举第一个节点的子节点(它的出边)
dfs(1,0,1);
void dfs(int x,int pr,int p){//x表示当前节点编号,pr表示当前节点的上一个节点,p表示要染的颜色
dp[x]=p;
for(auto i:g[x]){
//当x的子节点i等于上一个节点时,因为上一节点已染色,不继续递归
if(i==pr) continue;
dfs(i,x,1-p);//递归到子节点
}
}
分析
可以把'd'看成一种颜色,'p'是另一种,'?'是未染色,输入的u,v代表给第u个节点与第v个节点染上相反的颜色,如果和已有的颜色冲突,则输出-1,否则输出染色结果
完整代码
const int N=2e5+5;
vector<int>g[N];
int dp[N];
void dfs(int x,int pr,int p){
dp[x]=p;
for(auto i:g[x]){
if(i==pr) continue;
dfs(i,x,1-p);
}
}
void solve(){
int n,u,v;string s;cin>>n>>s;
s=' '+s;//移位,使字符串下标从1开始
rep(i,1,n-1){
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0,1);
// rep(i,1,n) cout<<dp[i]<<" ";
// cout<<endl;
set<int>d,p;//存下d或p的位置节点的颜色种类
for(int i=1;i<=n;i++){
//cout<<s[i]<<" "<<dp[i]<<endl;
if(s[i]=='d') d.insert(dp[i]);
if(s[i]=='p') p.insert(dp[i]);
}
//cout<<(*d.begin())<<" "<<(*p.begin())<<endl;
//当d或p的位置节点存到颜色超过一种,输出-1
if(d.size()>1||p.size()>1){//不能是!=1,因为?dd? 中p.size()=0
cout<<-1<<endl;
return ;
}
//当d,p位置颜色相同,输出-1
else if(d.size()==1&&p.size()==1&&*d.begin()==*p.begin()){
cout<<-1<<endl;
return ;
}
if(*d.begin()==1){
rep(i,1,n){
if(dp[i]) cout<<"d";
else cout<<"p";
}
}
else{
rep(i,1,n){
if(dp[i]) cout<<"p";
else cout<<"d";
}
}
}
D-小红数组操作_牛客周赛 Round 31
数组模拟链表
两个操作:
- 输入1 x y,将x插入在元素y的右边。保证此时数组中没有元素等于x,且数组中存在一个y。特殊的,如果将x插入在数组的最左边,则y=0
- 输入2 x,将元素x删除。
#include<bits/stdc++.h>
using namespace std;
map<int,int>l,r;//l[i]与r[i]分布代表第i个元素的左右节点
int main(){
int n,q,x,y,op;
cin>>q;
while(q--){
cin>>op>>x;
if(op==1){//将x插入y的右边
cin>>y;
if(r.count(y)){
r[x]=r[y];
l[r[y]]=x;
}
l[x]=y;
r[y]=x;
}
else{//将元素x删除
if(l.count(x)){//x有前驱
if(r.count(x)) r[l[x]]=r[x];
else r.erase(l[x]);
}
if(r.count(x)){
if(l.count(x)) l[r[x]]=l[x];
else l.erase(r[x]);
}
l.erase(x),r.erase(x);
}
}
x=0;
vector<int>out;
//输出整个数组
while(r.count(x)){
x=r[x];
out.push_back(x);
}
cout<<out.size()<<"\n";
for(auto i:out) cout<<i<<" ";
}
数字之和
很经典的01背包问题,
但我就是一点dp方向的思路都没有,因为数据量小,也可以用每次输入遍历set来更新
解法1
因为sum(a[i])最大为10000,可以对每个a[i]枚举它的所有合法的加和值,这样就能得到所有a数组可能的权值
也就是把当前的a[i]与i以前的每a[i]相加,加和值用数组v标记下来
代码
const int N=1e4+5;
int a[N],v[N];
void solve(){
int n,sum=0;cin>>n;
rep(i,1,n){
cin>>a[i];
sum+=a[i];
}
v[0]=1;
rep(i,1,n){
per(j,sum,0){
if(v[j]&&j+a[i]<=sum){
v[j+a[i]]=1;
}
}
}
int ans=0;
rep(i,1,sum){
if(v[i]) ans++;
}
cout<<ans;
}
解法2
每次输入遍历set,把加和值存进tmp里,再把tmp的元素插入set中
const int N=1e3+5;
int a[N];
set<int>s;
void solve(){
int n,x;cin>>n;
rep(i,1,n){
cin>>x;
vector<int>tmp;
for(auto it:s){
if(!s.count(it+x)) tmp.push_back(it+x);
}
for(auto it:tmp) s.insert(it);
s.insert(x);
}
cout<<s.size();
}
解法3(最优解)
每种状态用dp存下来,dp[j]表示前i个数,组成的和为j是否可行,可行为1,不可行为0,这样答案ans=sum(dp[j]==1)。初始化dp[0]=1,转移方程:dp[j]|=dp[j-a[i]]
代码
const int N=1e4+5;
int dp[N],a[N];
void solve(){
int n,sum=0;cin>>n;
rep(i,1,n){
cin>>a[i];
sum+=a[i];
}
dp[0]=1;
rep(i,1,n){
per(j,sum,a[i]){
dp[j]|=dp[j-a[i]];
}
}
int ans=0;
rep(i,1,sum){
if(dp[i]) ans++;
}
cout<<ans;
}
几番烟雾,只有花难护
数论分块里的向上取整
知识点&分析
详见数论分块
代码
const int M=998244353;
int fp(int b,int p){
int res=1;
while(p){
if(p&1) res=res*b%M;
b=b*b%M;
p>>=1;
}
return res;
}
int s=fp(6,M-2)%M;
int f(int n){
return((n*(n+1)%M*(n*2+1)%M)%M*s)%M;
}
void solve(){
int n;cin>>n;
//cout<<f(0)<<" "<<f(1)<<" "<<f(2)<<" "<<f(3)<<endl;
int ans=0;
for(int l=1,r,k;l<=n;l=r+1){
k=(n+l-1)/l;
if(k==1) r=n;
else r=(n-1)/(k-1);
//cout<<l<<" "<<r<<" "<<k<<endl;
ans+=k*(f(r)-f(l-1)+M)%M;
}
cout<<(ans+M)%M<<endl;
}
1715-C: 秒了 -- Power OJ
考察模法的性质🐻❄️
分析
模法结果实际上是求余,所以我们可以模拟除法,求出最后的余数
操作
代码
int get_len(int n){
int len=0;
while(n){
len++;
n/=10;
}
return len;
}
void solve(){
string a;int b;
cin>>a>>b;
int s=0;
int la=a.length(),lb=get_len(b);
int now=0;
for(int i=0;i<la;i++){
s=s*10+a[i]-'0';
now++;
if(now==lb&&s>b||now>lb){
s%=b;
now=get_len(s);
}
}
cout<<s<<endl;
}
P1434 [SHOI2002] 滑雪
赛时有道题的原题是这个,是很直观的dfs(⊙﹏⊙),但封榜了才一次过……后悔自己没早点看😢
赛时只会没有一点剪枝的dfs……不过赛后也补了记忆化的写法,结合我原来的写法,对记忆化搜索更熟悉了一点吧(?)
代码1(纯暴力,会t)
int a[N][N],v[N][N],n,m,out;
int op[4][2]={0,1,0,-1,1,0,-1,0};
void dfs(int x,int y,int step){
int f=1;
rep(k,0,3){
int nx=x+op[k][0],ny=y+op[k][1];
if(v[nx][ny]||a[nx][ny]>=a[x][y]||nx<1||ny<1||nx>n||ny>n) continue;
v[nx][ny]=1;
f=0;
dfs(nx,ny,step+1);
v[nx][ny]=0;
}
if(f){
out=max(out,step);
}
}
void solve(){
cin>>n>>m;
rep(i,1,n){
rep(j,1,m){
cin>>a[i][j];
v[i][j]=0;
}
}
int ans=0;
rep(i,1,n){
rep(j,1,m){
out=0;
dfs(i,j,1);
//cout<<out<<" ";
ans=max(out,ans);
}
}
cout<<ans;
}
代码2(记忆化搜索)
const int N=5e2+5;
int a[N][N],mem[N][N],n,m,out;
int op[4][2]={0,1,0,-1,1,0,-1,0};
int dfs(int x,int y){
if(mem[x][y]) return mem[x][y];
mem[x][y]=1;
rep(k,0,3){
int nx=x+op[k][0],ny=y+op[k][1];
if(a[nx][ny]>=a[x][y]||nx<1||ny<1||nx>n||ny>n) continue;
dfs(nx,ny);
mem[x][y]=max(mem[x][y],mem[nx][ny]+1);
}
return mem[x][y];
}
void solve(){
cin>>n;
rep(i,1,n){
rep(j,1,n){
cin>>a[i][j];
mem[i][j]=0;
}
}
int ans=0;
rep(i,1,n){
rep(j,1,n){
cout<<dfs(i,j)<<" ";
}
cout<<endl;
}
cout<<endl;
}
2.二进制王国
拼接字符串使得字典顺序最小,需要重定义排序规则
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
string s[N];
int main()
{
int n;cin>>n;
for(int i=0;i<n;i++){
cin>>s[i];
}
sort(s,s+n,[](string &a,string &b){
return a+b<b+a;//lamda表达式重定义排序规则
});
for(int i=0;i<n;i++) cout<<s[i];
return 0;
}
3.djwcb
知识点
高精度模法——实际是在模拟除法的过程得余数
string s;int mod;cin>>s>>mod;
int l=p.length();
int res=0;
rep(i,0,l-1){
res=res*10+p[i]-'0';
res%=4;
}
cout<<res;
分析
代码
void solve(){
int x;string p;
cin>>x>>p;
x%=10;
int l=p.length();
int s=0;
rep(i,0,l-1){
s=s*10+p[i]-'0';
s%=4;
}
if(!s) s=4;
int res=1;
while(s--){
res*=x;
}
cout<<res%10;
cout<<endl;
}
5.无理数位数查询
细节很多的模拟题
思路
已知m进制下的k位数的数字个数为
-
用
就可以得到第n个数是k位数 -
再利用
可以得到第n个数是k位数下第cur个整数 -
由
可以得到第n个数是m进制下的第cur个整数 -
而
则可以得到它是k位数下第cur个整数的第w位(从高位起)
最后再做一下进制转换,把cur转换为m进制数,我们的答案就是转换后的从高位起的第w个数
代码
const int N=66;
int a[N],p[N];
int m;
int qpow(int b,int p){
int res=1;
while(p){
if(p&1) res*=b;
b=b*b;
p>>=1;
}
return res;
}
int f(int k){
if(k) return (k*(m-1)*qpow(m,k-1));
else return 0;
}
void solve(){
int n;cin>>n>>m;
//cout<<qpow(m,0)<<endl;
rep(i,1,66){
//cout<<f(i)<<" ";
p[i]=p[i-1]+f(i);
}
//cout<<endl;
int k=1;
while(p[k]<n) k++;
//cout<<k<<endl;
int cur=n-p[k-1];//f(k-1);
int w=(cur+k-1)%k+1;//第cur个整数的第w位
cur=(cur+k-1)/k;//k位数下第cur个整数
//cout<<cur<<" "<<w<<endl;
cur+=qpow(m,k-1)-1;//m进制下的第cur个整数
//cout<<cur<<endl;
int e=0;
while(cur){//进制转换
a[++e]=cur%m;
cur/=m;
}
// per(i,e,1) cout<<a[i];
// cout<<endl;
cout<<a[e-w+1]<<endl;
}
1815: 斐波那契数列推论
学长推荐的矩阵快速幂
const int M=10007;
struct matrix{
int c[3][3];
matrix(){
memset(c,0,sizeof(c));
}
}F,A;
matrix operator*(matrix &a,matrix &b){
matrix t;
rep(i,1,2){
rep(j,1,2){
rep(k,1,2){
t.c[i][j]=(t.c[i][j]+a.c[i][k]*b.c[k][j])%M;
}
}
}
return t;
}
void qpow(int n){
F.c[1][1]=F.c[1][2]=1;
A.c[1][1]=A.c[1][2]=A.c[2][1]=1;
while(n){
if(n&1) F=F*A;
A=A*A;
n>>=1;
}
}
int f(int n){
if(!n) return 0;
if(n<=2){
return 1;
}
mem(F.c),mem(A.c);
qpow(n-2);
return F.c[1][1];
}
void solve(){
int s,t;
while(cin>>s>>t){
if(s>t) swap(s,t);
//cout<<f(s)<<" "<<f(t)<<endl;
int ans=(f(t)*f(t+1)%M-f(s-1)*f(s)%M+M)%M;
cout<<ans<<endl;
}
}
E - Insert or Erase (atcoder.jp)
链表
map<int,int>l,r;//节点的前驱与后驱
void solve(){
int n,op,x,y;cin>>n;
rep(i,1,n) cin>>a[i];
rep(i,1,n){
l[a[i]]=a[i-1];
r[a[i]]=a[i+1];
}
r[a[n]]=-1,l[a[1]]=-114514;
r[-114514]=a[1];
int q;cin>>q;
while(q--){
cin>>op>>x;
if(op==1){
cin>>y;
r[y]=r[x];
l[y]=x;
l[r[x]]=y;
r[x]=y;
}
else{
l[r[x]]=l[x];
r[l[x]]=r[x];
}
}
int now=-114514;
while(r[now]!=-1){
now=r[now];
cout<<now<<" ";
}
}
C. Beautiful Triple Pairs
容斥原理
假设有两个集合A和B,我们想要求出这两个集合的并集的大小。根据容斥原理,我们不能简单地将A和B的大小相加,因为这样可能会重复计算一部分元素。
容斥原理告诉我们,要求A和B的并集的大小,我们首先要将A和B的大小相加,然后减去A和B的交集的大小,即:
这样我们就可以避免重复计算,得到A和B的并集的大小。
/*
8
2 1 1 2 1 1 1 1
2 1 1
1 1 2
1 2 1
2 1 1 ans+=3,ans-=3;
1 1 1 ans+=1+1+2,ans-=0;
1 1 1 ans+=2+2+3,ans-=1*3;
*/
void solve(){
int n;cin>>n;
rep(i,1,n) cin>>a[i];
map<array<int,3>,int>cnt;
map<pair<int,int>,int>mp1,mp2,mp3;
int ans=0;
rep(i,1,n-2){
ans+=mp1[{a[i],a[i+1]}]++;
ans+=mp2[{a[i],a[i+2]}]++;
ans+=mp3[{a[i+1],a[i+2]}]++;
ans-=cnt[{a[i],a[i+1],a[i+2]}]*3ll;
cnt[{a[i],a[i+1],a[i+2]}]++;
//cout<<mp1[{a[i],a[i+1]}]<<" "<<mp2[{a[i],a[i+2]}]<<" "<<mp3[{a[i+1],a[i+2]}]<<endl;
//cout<<cnt[{a[i],a[i+1],a[i+2]}]<<" "<<ans<<endl;
}
cout<<ans<<endl;
}
D-小蓝的二进制询问_河南萌新联赛2024第(一)场:河南农业大学
求[l,r]内二进制下1的个数的和
const int M=998244353;
int fp(int b,int p){
if(p<0) return 0;
int res=1;
while(p){
if(p&1) res=(b*res)%M;
b=(b*b)%M;
p>>=1;
}
return res;
}
void solve(){
int l,r;cin>>l>>r;
auto calc=[&](int n) -> int{
if(n<=1) return n;
int res=0,tmp=n;
per(i,61,0){
if((n>>i)&1){
res=((res+i*fp(2,i-1)%M)%M+1)%M+(M+tmp-fp(2,i))%M;
res%=M;
tmp=(M+tmp-fp(2,i))%M;
if(tmp==0) break;
}
}
return res%M;
};
cout<<(M+calc(r)-calc(l-1))%M<<endl;
}
C - Make Them Narrow (atcoder.jp)
很经典的枚举
[SCOI2009]游戏
LCM 完全背包
tokitsukaze and Soldier
学长推荐的思维题
我还要遇见几个你
需要求组合数
比大小 (hard version)
字符串哈希,类似于将字符串作进制数转换,hash[i]=hash[i-1]*131+a[i];
同时需要储存每位进制
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】