目录
关于10.05标准化测试:
T1:
题目描述:
[HNOI2011]数学作业 - 洛谷
https://www.luogu.com.cn/problem/P3216
题解:
刚开始的时候比较蒙圈,不晓得应该怎么写,于是就先写了一个暴力。接着就是从简单的入手,发现10以内的递推矩阵比较简单,因而想到可以分段进行矩阵递推
1~9为一段,10~99为一段,100~999为一段…………
接下来是如何构造矩阵了,发现单一的一个矩阵并不能满足,所以就采用了分段矩阵,1~9对应一个矩阵,10~99对应一个矩阵,以此类推。
然后是矩阵的维度问题,开始认为一个二维就可以了,但是二维矩阵不能实现依次+1的效果,所以直接三维矩阵:
1~9: 10~99:
100~999:
…………
要注意一下对于每一个矩阵的值都要对m取模。
(自己矩阵每次写都要想很久,想的很慢,所以一下的模式有必要单独提出来,太菜了没办法TAT)
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
for(int k=0;k<3;k++){
pp[i][j]=(pp[i][j]+ret[i][k]*now[k][j])%m;
}
}
}
for(int i=0;i<3;i++)for(int j=0;j<3;j++)ret[i][j]=pp[i][j];
for(int i=0;i<3;i++)for(int j=0;j<3;j++)pp[i][j]=0;
上标码:
#include<cstdio>
using namespace std;
unsigned long long n,m;
unsigned long long pe[3][3]={{10,0,0},{1,1,0},{0,1,1}};
unsigned long long hk[3][3]={{10,0,0},{1,1,0},{0,1,1}};
unsigned long long ans[3];
unsigned long long sd[3];
inline void qpow(unsigned long long t)
{
unsigned long long ret[3][3]={{1,0,0},{0,1,0},{0,0,1}};
unsigned long long now[3][3];
unsigned long long pp[3][3]={{0,0,0},{0,0,0},{0,0,0}};
for(int i=0;i<3;i++)for(int j=0;j<3;j++)now[i][j]=pe[i][j];
while(t){
if(t&1){
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
for(int k=0;k<3;k++){
pp[i][j]=(pp[i][j]+ret[i][k]*now[k][j])%m;
}
}
}
for(int i=0;i<3;i++)for(int j=0;j<3;j++)ret[i][j]=pp[i][j];
for(int i=0;i<3;i++)for(int j=0;j<3;j++)pp[i][j]=0;
}
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
for(int k=0;k<3;k++){
pp[i][j]=(pp[i][j]+now[i][k]*now[k][j])%m;
}
}
}
for(int i=0;i<3;i++)for(int j=0;j<3;j++)now[i][j]=pp[i][j];
for(int i=0;i<3;i++)for(int j=0;j<3;j++)pp[i][j]=0;
t>>=1;
}
for(int i=0;i<3;i++)for(int j=0;j<3;j++)hk[i][j]=ret[i][j];
}
int main(void)
{
scanf("%lld%lld",&n,&m);
unsigned long long tk=9;
ans[0]=0;ans[2]=1;ans[1]=1;
sd[0]=0;sd[2]=1;sd[1]=1;
//n--;
while(n){
if(tk>n)tk=n;
n-=tk;
for(int i=0;i<3;i++)for(int j=0;j<3;j++)hk[i][j]=pe[i][j];
qpow(tk);
sd[0]=(ans[0]*hk[0][0]+ans[1]*hk[1][0]+ans[2]*hk[2][0])%m;
sd[1]=(ans[0]*hk[0][1]+ans[1]*hk[1][1]+ans[2]*hk[2][1])%m;
sd[2]=(ans[0]*hk[0][2]+ans[1]*hk[1][2]+ans[2]*hk[2][2])%m;
ans[0]=sd[0];ans[1]=sd[1];ans[2]=sd[2];
tk*=10;
pe[0][0]=(pe[0][0]*10)%m;
}
printf("%llu",ans[0]);
return 0;
}
T2:
题目描述:
[HNOI2011]勾股定理 - 洛谷
https://www.luogu.com.cn/problem/P3213
前置知识及证明(话说我是真的屑,初中白学了?)
令
所以有:
利用这个公式就可便利的求出1e6中所有互质勾股数对
(K==i,A==j)枚举
,满足
不同奇偶,并且
void preWork()
{
for(LL i=2;(i<<1)<=M;++i)
for(LL j=1;(j*i<<1)<=M && j<i ;++j)
if(i*i-j*j<=M && (i&1)!=(j&1) && gcd(i,j)==1)
{
int a=i*i-j*j,b=i*j<<1;
add(a,b);add(b,a);
}
return ;
}
其实这道题对于我现在的水平好像就至此为止了,接下来就是优美的 代 码 欣 赏 片段
尽力写了一些注释,渴望着理解,然后g了
主函数:
int main()
{
read(n);
Mi[0]=1;
for(int i=1;i<=n;++i)
{
Mi[i]=(Mi[i-1]<<1)%Mod;
read(H[i]);
in[H[i]]++;
}
sort(H+1,H+1+n);
preWork();
LL ans=1;
for(int i=1;i<=n;i++)
if(!vis[H[i]])
ans=ans*solve(H[i])%Mod;
ans--;
print(ans);
return 0;
}
dfs:求出以为x为根的连通块
void dfs(int x,int f)
{
vis[x]=1;
zhan[++sum]=x;//入栈
for(int i=h[x];i;i=pre[i])
if(in[to[i]]&&to[i]!=f)//如果in不等于零即在序列中出现过 并且v!=fath
{
if(!vis[to[i]]) dfs(to[i],x);//未被访问过
else out[i]=1,edge[++edge[0]]=i,col[to[i]]=1,col[x]=1;
//else 执行表示在图里面存在环
//将此边进行标记,并且记录边的号数。
}
}
DFS:对于每一个连通块进行答案的统计
void DFS(int x,int n)
{
if(x>n)
{
for(int i=1;i<=edge[0];i++)
{
int a=to[edge[i]],b=to[edge[i]^1];
if(must[a]==1 && must[b]==1) return ;
}
DP(zhan[1],0);
Ans=(Ans+dp[zhan[1]][0]+dp[zhan[1]][1])%Mod;
return ;
}
must[w[x]]=1,DFS(x+1,n);
must[w[x]]=0,DFS(x+1,n);
}
DP:nb的树上DP,求出独立集的方案数(类似没有上司的舞会)
设dp[i][0]dp[i][0]表示在ii这棵子树上不选ii的方案数,dp[i][1]dp[i][1]表示在ii这颗子树上选ii的方案数。
状态转移长这样:
num[i]表示i出现了几次。
我们发现我们建的图将构成一片森林。
假设森林里每颗树的根是,那么答案就是:
之后再减1,即去掉空集。
这样就是三十分,成功了!
void DP(int x,int f)
{
dp[x][0]=1,dp[x][1]=Mi[in[x]]-1;
if(col[x]&&must[x]==1) dp[x][0]=0;
if(col[x]&&must[x]==0) dp[x][1]=0;
if(h[x]==0) return ;
for(int i=h[x];i;i=pre[i])
if(out[i]==0&&to[i]!=f)//这条边不是环上的边,并且v!=fath
{
int y=to[i];
if(in[y]==0) continue;//如果这个点不在序列中,就跳过
DP(y,x);
dp[x][0]=dp[x][0]*(dp[y][0]+dp[y][1])%Mod;
dp[x][1]=dp[x][1]*dp[y][0]%Mod;
}
return ;
}
完整代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
# define M 1000000
# define Mod 1000000007
# define LL long long
using namespace std;
template<typename T>inline void read(T &x)
{
T ch=getchar(),xx=1;x=0;
while(!isdigit(ch))xx=ch=='-'?-1:xx,ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
x*=xx;
}
template<typename T>inline void print(T x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)print(x/10);
putchar(x%10+'0');
}
int n,H[M+5],in[M+5],Mi[M+5],dp[M+5][2],zhan[M+5],edge[M+5],w[M+5];
int d,h[M+5],to[M+5],pre[M+5];
bool vis[M+5],must[M+5],col[M+5],out[M+5];
int gcd(int a,int b)
{
if(b==0) return a;
return gcd(b,a%b);
}
void add(int a,int b)
{
d++;
pre[d]=h[a];
to[d]=b;
h[a]=d;
}
void DP(int x,int f)
{
dp[x][0]=1,dp[x][1]=Mi[in[x]]-1;
if(col[x]&&must[x]==1) dp[x][0]=0;
if(col[x]&&must[x]==0) dp[x][1]=0;
if(h[x]==0) return ;
for(int i=h[x];i;i=pre[i])
if(out[i]==0&&to[i]!=f)//这条边不是环上的边,并且v!=fath
{
int y=to[i];
if(in[y]==0) continue;//如果这个点不在序列中,就跳过
DP(y,x);
dp[x][0]=dp[x][0]*(dp[y][0]+dp[y][1])%Mod;
dp[x][1]=dp[x][1]*dp[y][0]%Mod;
}
return ;
}
int sum;
void dfs(int x,int f)
{
vis[x]=1;
zhan[++sum]=x;//入栈
for(int i=h[x];i;i=pre[i])
if(in[to[i]]&&to[i]!=f)//如果in不等于零即在序列中出现过 并且v!=fath
{
if(!vis[to[i]]) dfs(to[i],x);//未被访问过
else out[i]=1,edge[++edge[0]]=i,col[to[i]]=1,col[x]=1;
//else 执行表示在图里面存在环
//将此边进行标记,并且记录边的号数。
}
}
void preWork()
{
for(LL i=2;(i<<1)<=M;++i)
for(LL j=1;(j*i<<1)<=M && j<i ;++j)
if(i*i-j*j<=M && (i&1)!=(j&1) && gcd(i,j)==1)
{
int a=i*i-j*j,b=i*j<<1;
add(a,b);add(b,a);
}
return ;
}
LL Ans;
void DFS(int x,int n)
{
if(x>n)
{
for(int i=1;i<=edge[0];i++)
{
int a=to[edge[i]],b=to[edge[i]^1];
if(must[a]==1 && must[b]==1) return ;
}
DP(zhan[1],0);
Ans=(Ans+dp[zhan[1]][0]+dp[zhan[1]][1])%Mod;
return ;
}
must[w[x]]=1,DFS(x+1,n);
must[w[x]]=0,DFS(x+1,n);
}
LL solve(int x)
{
sum=edge[0]=w[0]=0;
dfs(x,0);
//得到了栈,即得到了这一个连通块的编号
Ans=0;
for(int i=1;i<=sum;++i)
if(col[zhan[i]])
w[++w[0]]=zhan[i];
DFS(1,w[0]);
return Ans;
}
int main()
{
read(n);
Mi[0]=1;
for(int i=1;i<=n;++i)
{
Mi[i]=(Mi[i-1]<<1)%Mod;//预处理出2的n次幂
read(H[i]);
in[H[i]]++;//是否存在,对于后面的跑图服务
}
sort(H+1,H+1+n);
preWork();
LL ans=1;
for(int i=1;i<=n;i++)
if(!vis[H[i]])
ans=ans*solve(H[i])%Mod;
ans--;
print(ans);
return 0;
}
关于正解,可以前往这里阅读神仙的题解
关于10.06标准化测试:
T1:
题目描述:
https://www.luogu.com.cn/problem/P3217
https://www.luogu.com.cn/problem/P3217
题解:
自己做的时候还是想到了和正解一样的匹配方式,就是求出每一条线段,并且记录中点x,y和长度这些信息。但是,DT的把映射写错了,使得匹配过程效率极低,string的不便便在此体现出来。然而正解不用建立映射,而是排序(我没想到QAQ),按照中点坐标,长度进行排序,然后扫一遍,而对于一段中点相等,长度也相等的线段,进行暴力求答案。
像这样:
sort(ak+1,ak+1+m,tmp);
for(int i=1,j=1;i<=m;i=j){
while(j<=m && ak[j].len==ak[i].len && ak[j].midx==ak[i].midx && ak[j].midy==ak[i].midy)j++;
for(int k=i;k<j;k++){
for(int l=k+1;l<j;l++)
ans=max(ans,abs(ak[k].lix*ak[l].liy-ak[k].liy*ak[l].lix)>>1);
}
}
然后就是对于如何快速求出矩形的面积这个问题,记录下矩形四个顶点然后求答案是可以的,但是这里有一个更加高级的操作:求叉积(了解即可,我还是蒙的)
现在该解决的都解决了,上马!
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
struct Node{
long long lix,liy;
long long midx,midy;
long long len;
}ak[2250010];
struct NNN{
long long x,y;
}a[1510];
int n,m;
long long ans;
template <typename T>inline void in(T &x)
{
T ch=getchar(),xx=0,fw=1;
while(!isdigit((int)ch)){if(ch=='-')fw=-1;ch=getchar();}
while(isdigit((int)ch)){xx=(xx<<1)+(xx<<3)+ch-'0';ch=getchar();}
x=xx*fw;
}
inline long long getlen(int xx,int yy)
{
return (a[xx].x-a[yy].x)*(a[xx].x-a[yy].x)+(a[xx].y-a[yy].y)*(a[xx].y-a[yy].y);
}
inline bool tmp(Node aa,Node bb)
{
return aa.len==bb.len?aa.midx==bb.midx?aa.midy<bb.midy:aa.midx<bb.midx:aa.len>bb.len;
}
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
in(a[i].x);in(a[i].y);
for(int j=1;j<i;j++){
m++;
ak[m].lix=a[i].x-a[j].x;
ak[m].liy=a[i].y-a[j].y;
ak[m].midx=a[i].x+a[j].x;
ak[m].midy=a[i].y+a[j].y;
ak[m].len=getlen(i,j);
}
}
ans=0;
sort(ak+1,ak+1+m,tmp);
for(int i=1,j=1;i<=m;i=j){
while(j<=m && ak[j].len==ak[i].len && ak[j].midx==ak[i].midx && ak[j].midy==ak[i].midy)j++;
for(int k=i;k<j;k++){
for(int l=k+1;l<j;l++)
ans=max(ans,abs(ak[k].lix*ak[l].liy-ak[k].liy*ak[l].lix)>>1);
}
}
printf("%lld",ans);
}
T3:
题目描述:
https://www.luogu.com.cn/problem/P3218
https://www.luogu.com.cn/problem/P3218
题解:
一测拿到题目的时候毫无头绪,然后看了题解之后,发现这道题其实还好,但是有一个大难点就是证明:当每段速度基本相同的时候,答案是最优秀的。here
题目定义是当前段每单位所耗的油量,s是斜率,所以当为下坡时,这个油耗一定是0(它不能下坡自动加油)而
都是给出的,对于
每一段都是固定的,所以一定满足单调性,说道单调性,那就二分!
知道了这样的性质之后,就可以直接二分答案,二分出使总油耗不超出油耗的最大速度。
然而光这样是不行的,因为下坡时满足,但是有个问题,就是车子的最大速度是被限制了的,所以在两者之间取小的那个。然而什么时候输出IMPOSSIBLE?就是当二分得到的速度为零,并且道路有上坡的时候(因为如果全是下坡,那就可以不耗油的滑下去)
该考虑的都考虑了,上马!
#include<cstdio>
#include<algorithm>
#include<cmath>
#define emp 1e-15//处理精度
using namespace std;
struct Node{
double x,y,k,len;
inline void in(){
scanf("%lf%lf",&x,&y);
x=x/1000.0;y=y/1000.0;
k=(double)y/x;
len=sqrt((x*x)+(y*y));
}
}C[10010];
int T;
double a,b,maxv,f;
int r;
inline double maxx(double aa,double bb)
{
return aa-bb>emp?aa:bb;
}
inline double minn(double aa,double bb)
{
return aa-bb<emp?aa:bb;
}
inline bool check(double tk)
{
double ret=0.0;
for(int i=1;i<=r;i++){
ret+=maxx(a*tk+b*C[i].k,0)*C[i].len;
if(ret>f+emp)return false;
}
return true;
}
inline double getans(double v)
{
double ret=0.0;
for(int i=1;i<=r;i++){
double now=a*v+b*C[i].k;
if(now>emp){
if(v<=emp)return 0;
ret+=(double)C[i].len/v;
}else{
double vv=(double)(-1.0*b*C[i].k)/a;
vv=minn(vv,maxv);
ret+=C[i].len/vv;
}
}
return ret;
}
inline void solve()
{
double lef=0.0,rig=maxv;
int t=0;
while(t<2000){
double mid=(lef+rig)/2.0;
if(check(mid))lef=mid;
else rig=mid;
t++;
}
double ans=getans(lef);
if(ans<=emp){
printf("IMPOSSIBLE\n");
}else printf("%.5lf\n",ans);
}
int main(void)
{
scanf("%d",&T);
while(T--){
scanf("%lf%lf%lf%lf%d",&a,&b,&maxv,&f,&r);
for(int i=1;i<=r;i++)C[i].in();
solve();
}
}
关于把一些题转化成图论:
2-SAT
先看一道题:
满汉全席https://www.luogu.com.cn/problem/P4171
https://www.luogu.com.cn/problem/P4171 这道题就是2-SAT中的典中典,对于每一个评测官,要求必须满足他的两个要求的其中一个,那么一个评测官所有的要求可能情况:hh,hm,mh(怪物猎人 大雾),mm。
对于hh:
如果第一个选了m,那么必须第二个选h才能满足的,所以第一个为m,第二个为汗就被捆绑起立来了,同理,第二个选了m,第一个就得选h,同样捆绑
对于hm
如果第一个选了m,那么必须第二个选m才能满足的,所以第一个为m,第二个为汗就被捆绑起立来了,同理,第二个选了h,第一个就得选h,同样捆绑
。。。。。。
想到这样捆绑,可能想到并查集或者是建图,然后我们发现,对于hh这种情况,第二个选了h的话,第一个不是必须选m,所以这样的捆绑是单向的,图!
现在,我们的2-SAT就出场了,2-SAT,通俗的讲就是,每个东东会给你两个条件,对于这两个条件,可以是用与、或、非……等连接起来,这道题就是或,即满足一个即可。将hh,hm这些条件进行一个单向边的建,不难看出建出来的图是一个个连通块。
那么如何判断呢?Tarjan又来了,判断一个食材 的h,和m是不是在同一个连通块里面,不能用简单的DFS染色,会出大问题,那咱们就整个强连通分量。
这是一个正常的求dfn和low(复习下)
inline void tarjan(int now)
{
cnt++;
dfn[now]=low[now]=cnt;
instack[now]=1;
sta.push(now);
for(int i=Head[now];i;i=tr[i].nxt){
int v=tr[i].v;
if(!dfn[v]){
tarjan(v);
low[now]=min(low[now],low[v]);
}
else if(instack[v]){
low[now]=min(low[now],dfn[v]);
}
}
}
接在这下面的比较核心,用好stack!
inline void tarjan(int now)
{
cnt++;
dfn[now]=low[now]=cnt;
instack[now]=1;
sta.push(now);
for(int i=Head[now];i;i=tr[i].nxt){
int v=tr[i].v;
if(!dfn[v]){
tarjan(v);
low[now]=min(low[now],low[v]);
}
else if(instack[v]){
low[now]=min(low[now],dfn[v]);
}
}
if(dfn[now]==low[now]){
int top;
coll++;
do{
top=sta.top();
col[top]=coll;
instack[top]=0;
sta.pop();
}while(top!=now);
}
}
完整代码:
#include<cstdio>
#include<cstring>
#include<string>
#include<stack>
#include<algorithm>
#include<iostream>
using namespace std;
struct Node{
int v,nxt;
}tr[2000200];
int Head[2000200];
int kok;
int T,n,m;
int col[2002000];
int cnt;
bool flag;
inline void add(int x,int y)
{
kok++;
tr[kok].v=y;tr[kok].nxt=Head[x];
Head[x]=kok;
}
int coll=0;
int dfn[2000100],low[2000100];
int instack[2000100];
stack<int>sta;
inline void tarjan(int now)
{
cnt++;
dfn[now]=low[now]=cnt;
instack[now]=1;
sta.push(now);
for(int i=Head[now];i;i=tr[i].nxt){
int v=tr[i].v;
if(!dfn[v]){
tarjan(v);
low[now]=min(low[now],low[v]);
}
else if(instack[v]){
low[now]=min(low[now],dfn[v]);
}
}
if(dfn[now]==low[now]){
int top;
coll++;
do{
top=sta.top();
col[top]=coll;
instack[top]=0;
sta.pop();
}while(top!=now);
}
}
inline void init()
{
memset(col,0,sizeof(col));
memset(Head,0,sizeof(Head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
kok=0;flag=0;cnt=coll=0;
}
int main(void)
{
scanf("%d",&T);
char ch1[100],ch2[100];
while(T--){
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=m;i++){
cin>>ch1;
cin>>ch2;
int x=0,y=0,k;
k=1; while(ch1[k]>='0'&&ch1[k]<='9')x=x*10+ch1[k++]-'0';
k=1; while(ch2[k]>='0'&&ch2[k]<='9')y=y*10+ch2[k++]-'0';
if(ch1[0]=='m'){
if(ch2[0]=='m'){
add(x+n,y);
add(y+n,x);
}
else if(ch2[0]=='h'){
add(x+n,y+n);
add(y,x);
}
}else if(ch1[0]=='h'){
if(ch2[0]=='m'){
add(x,y);
add(y+n,x+n);
}
else if(ch2[0]=='h'){
add(x,y+n);
add(y,x+n);
}
}
}
for(int i=1;i<=n*2;i++)
if(!dfn[i])tarjan(i);
for(int i=1;i<=n;i++){
if(col[i]==col[i+n]){
flag=1;
break;
}
}
if(flag)printf("BAD\n");
else printf("GOOD\n");
}
}
差分约束系统:
对于此类题,我基础不过多叙述,这里是个非常好的详解
讲讲写差分约束应该注意的几点:
要分为建条件边和虚边(瞎取的名字)
条件边不多说,就是建立
到
长度为
的边
for(int i=1;i<=m;i++){
scanf("%d%d%d",&b,&e,&t);
add(b,e+1,t);
}
然而虚边就是,将1——N连接成一个链,边长取决于题目描述
例如种树就是这样建的:
for(int i=0;i<=n;i++){
add(i+1,i,-1);
add(i,i+1,0);
}
特别注意:跑最长路的时候,得用spfa因为有可能出现负数边
本文探讨了编程竞赛中的若干经典问题,包括矩阵快速幂、2-SAT与差分约束系统的解决策略。通过实例解析了如何运用数学思维解决实际问题,如洛谷上的数学作业与勾股定理题目,以及满汉全席问题的2-SAT求解。同时,文章还介绍了如何将线段树应用于求解复杂问题,以及如何在满汉全席问题中构建并判断2-SAT图的连通性。
&spm=1001.2101.3001.5002&articleId=120620903&d=1&t=3&u=94633e76f3b3449a8ff4afa246bf026a)
975

被折叠的 条评论
为什么被折叠?



