CSP题解

部分题解,主要是前四题的,有些代码忘记写注释,如有疑问欢迎私聊讨论。

2022-12 CSP

第2题 训练计划

利用fa数组维护每个结点的依赖结点,那么如果我们从前往后遍历结点的话,如果当前节点i没有依赖结点,那么最早开始时间一定为1(最早时间用g数组维护,即g[i]=1) ;如果有依赖结点,由于依赖结点一定小于当前节点,那么有g[i]=g[fa[i]]+t[fa[i]](有点一维dp的感觉哈)。更新g[i]的同时检查是否会超期,如果有超期置flag=1后面就不用求最晚开始时间了。

对于最晚开始的时间(用h数组维护),由于最晚开始时间会由子结点影响父节点(使得父节点不能太晚开始),考虑到父节点编号一定小于子结点编号的性质,我们倒序遍历结点,如果当前节点i没有依赖结点(也就是父节点),则h[i] = n - t[i] + 1;若有依赖结点,则尝试更新父节点的h,即h[fa[i]] = min(h[fa[i]], h[i] - t[fa[i]]);

本题最重要的就是依赖结点一定小于当前节点这个性质,最开始我就忘记了这个性质,在那里写搜索。

//20:45
#include<bits/stdc++.h>
using namespace std;

int n,m;
int fa[105],t[105];
int g[105];//最早开始时间
int h[105];//最晚开始时间

int main(){
    cin>>n>>m;
    int flag = 1;
    for(int i=1;i<=m;++i){
        cin>>fa[i];
    }
    for(int i=1;i<=m;++i){
        cin>>t[i];
    }
    for(int i=1;i<=m;++i){
        if(!fa[i]) g[i] = 1;
        else{//fa[i]>0
            g[i] = g[fa[i]] + t[fa[i]];
        }
        if(g[i] + t[i] - 1 > n) flag = 0;
    }
    for(int i=1;i<=m;++i) cout<<g[i]<<" ";
    cout<<endl;
    if(flag == 0){
        return 0;
    }
    for(int i=1;i<=m;++i) h[i] = n;
    for(int i=m;i>0;--i){
        if(h[i] == n) h[i] = n - t[i] + 1;
        if(fa[i]){
            h[fa[i]] = min(h[fa[i]], h[i] - t[fa[i]]);
        }
    }
    for(int i=1;i<=m;++i) cout<<h[i]<<" ";
    cout<<endl; 
    return 0;
}

第3题 JPEG 解码

无需优化时间,放心按部就班的模拟就好了,只有量化矩阵的填充可能会花点时间来想怎么遍历,其他都比较简单,而且如果对于量化矩阵的填充实在想不出来如何用代码实现,直接挨个赋值在考场时间应该也是来得及的,感觉这次CSP拿个300分还是很容易滴~

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5 + 5;
int Q[10][10],M[10][10];
int n,T,a[70];
int dx[5]={0,1,1,-1}, dy[5]={1,-1,0,1};//方向移动数组
int dir = 0;//方向
int N[10][10];

void xr(){
    int i = 1, j = 1;
    M[1][1] = a[1];
    int t = 2;
    while(t<=n){
        if(dir == 0){//右移一个单位
            i += dx[dir];
            j += dy[dir];
            M[i][j] = a[t];
            t++;
            if(t < 36) dir = 1;
            else dir = 3;
        }
        else if(dir == 1){//朝左下角移动到边界
            while(j > 1 && i < 8){
                i += dx[dir];
                j += dy[dir];
                M[i][j] = a[t];
                t++;
                if(t > n) break;
            }
            if(t < 36) dir = 2;
            else dir = 0;
        }
        else if(dir == 2){//朝下走一个单位
            i += dx[dir];
            j += dy[dir];
            M[i][j] = a[t];
            t++;
            if(t < 36) dir = 3;
            else dir = 1;
        }
        else if(dir == 3){//朝右上角走到边界
            while(i > 1 && j < 8){
                i += dx[dir];
                j += dy[dir];
                M[i][j] = a[t];
                if(t == 44)cout<<"i = "<<i<<" j = "<<j<<endl;
                t++;
                if(t > n) break;
            }
            if(t < 36) dir = 0;
            else dir = 2;
        }
    }
}

void print0(){
    for(int i=1;i<=8;++i){
        for(int j=1;j<=8;++j){
            cout<<M[i][j]<<" ";
        }
        cout<<endl;
    }
}

void lh(){
    for(int i=1;i<=8;++i){
        for(int j=1;j<=8;++j){
            M[i][j] *= Q[i][j];
        }
    }
}

void bh(){
    for(int i=1;i<=8;++i){
        for(int j=1;j<=8;++j){
            //计算N[i][j]
            double res = 0;
            for(int u=1;u<=8;++u){
                for(int v=1;v<=8;++v){
                    double t1 = (u == 1 ? sqrt(1.0/2) : 1);
                    double t2 = (v == 1 ? sqrt(1.0/2) : 1);
                    res += t1 * t2 * M[u][v] * cos(acos(-1)/8.0*(i-1 + 1.0/2)*(u-1)) * cos(acos(-1)/8.0*(j-1+1.0/2)*(v-1));
                }
            }
            res /= 4.0;
            res += 128;
            if((int)(res * 2) > (int)res * 2)
                N[i][j] = (int)res + 1;        
            else N[i][j] = (int)res;
            N[i][j] = min(255, N[i][j]);
            N[i][j] = max(0, N[i][j]);
        }
    }
}

void print2(){
    for(int i=1;i<=8;++i){
        for(int j=1;j<=8;++j){
            cout<<N[i][j]<<" ";
        }
        cout<<endl;
    }
}

int main(){
    for(int i=1;i<=8;++i){
        for(int j=1;j<=8;++j){
            scanf("%d",&Q[i][j]);
        }
    }
    cin>>n>>T;
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
    }
    xr();//写入
    if(T == 0){
        print0();
        return 0;
    }
    lh();//量化
    if(T == 1){
        print0();
        return 0;
    }
    bh();//变换
    if(T == 2){
        print2();
    }
    return 0;
}

2022-09 CSP

第2题 何以包邮?

想到用01背包来做就很简单。

01背包解决的是从n个物品中选择放入容量为v的背包里的最大价值,在这题中我们可以把书价值和体积都用价格来表示,然后跑一遍01背包,就可以得到你手里有m(任意取值)的钱能从n本书里买到的最大价值f[n][m]。那我们只需要从j=x开始遍历,当f[n][j]>=x时,说明此时对应的价值即为大于等于x的最小价值

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 3e5 + 5;
int n,x;
int a[32],f[32][maxn];

int main(){
    cin>>n>>x;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    memset(f, 0, sizeof f);
    for(int i=1;i<=n;i++){
        for(int j=0;j<=maxn;j++){
            if(j >= a[i])
                f[i][j] = max(f[i-1][j], f[i-1][j-a[i]] + a[i]);
            else f[i][j] = f[i-1][j];
        }
    }
    for(int i=x;i<=maxn;i++){
        if(f[n][i] >= x){
            cout<<f[n][i]<<endl;
            return 0;
        }
    }
    return 0;
}

第3题 防疫大数据

最重要的点在于只处理当天和之前7天的漫游记录,超出7天的漫游记录和风险地区记录直接删去。

考虑到这一点的话,应该很容易拿到100分。

需要维护的东西就漫游记录和风险地区记录列表,区间检查暴力检查即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long

int n;
struct fxlb{
    int d, r;
};
vector<fxlb> F;
bool my_comp(fxlb a, fxlb b){return a.d < b.d;}

struct record{
    int d,u,r;
};
bool my_com(record a, record b){return a.d < b.d;}

vector<record> R;

bool check(int st,int ed, int r){
    unordered_map<int,int> flag;
    int cnt = 0;
    for(fxlb tmp : F){
        if(tmp.r != r || tmp.d > ed || tmp.d + 7 <= st) continue;
        int left = max(st, tmp.d);
        int right = min(ed, tmp.d + 6);
        for(int i=left;i<=right;++i){
            if(flag.count(i)==0) {flag[i] = 1; cnt++;}
            if(cnt == ed - st + 1) return true;
        }
    }
    return false;
}

void deal_record(int t){//处理第t天的record
    sort(F.begin(), F.end(), my_comp);
    while(!F.empty() && F[0].d < t - 14) F.erase(F.begin());
    sort(R.begin(), R.end(), my_com);
    while(!R.empty() && R[0].d <= t - 7) R.erase(R.begin());
    set<int> fxyh;
    for(record tmp : R){
        if(fxyh.find(tmp.u) != fxyh.end()) continue;
        if(check(tmp.d, t, tmp.r)){//如果检查r地区持续风险
            fxyh.insert(tmp.u);
        }
    }
    cout<<t<<" ";
    for(int tmp : fxyh){
        cout<<tmp<<" ";
    }
    cout<<endl;
}

int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
    cin>>n;
    for(int t=0;t<n;++t){
        int r,m,p;cin>>r>>m;
        for(int j=1;j<=r;++j){
            cin>>p;
            F.push_back(fxlb{t, p});
        }
        int d,u,rr;
        for(int i=1;i<=m;++i){
            cin>>d>>u>>rr;
            R.push_back(record{d,u,rr});
        }
        deal_record(t);//输出漫游记录
    }
    return 0;
}

第4题 吉祥物投票

单纯用数组维护每个作品和投票者只能得20的TLE

改成用区间段维护每个作品的区间段投票者,45分的TLE   认输orz

#include<bits/stdc++.h>
using namespace std;
#define ll long long

int maxn = 1e9 + 5;
const int maxm = 1e5 + 5;
int n,m,q;

struct node{
	int l,r,v;
	bool operator<(const node &b) const{
		return this->l == b.l ? this->r < b.r : this->l < b.l;
	}
};

set<node> st;

//编号为x的作品开展了一场拉票活动,成功地吸引了编号为l-r的投票者的兴趣,使得他们的投票意愿全部改为x。
void LP(int l,int r,int x){
	vector<node> vec2;
	vector<set<node>::iterator> vec;
	for(auto it = st.begin();it != st.end();it++){
		node t = (*it);
		if(t.r < l) continue;
		if(t.l > r) break;
		if(t.r <= r && t.l < l){
			t.r = l - 1;
			const_cast<node&> (*it) = node{t.l, t.r, t.v};
		}
		if(t.r <= r && t.l >= l) vec.push_back(it);
		if(t.r > r && t.l >= l){
			t.l = r + 1;
			const_cast<node&> (*it) = node{t.l, t.r, t.v};
		}
		if(t.l < l && t.r > r){
			vec2.push_back(node{t.l, l - 1, t.v});
			vec2.push_back(node{r + 1, t.r, t.v});
			vec.push_back(it);
		}
	}
	for(auto t : vec){
		st.erase(t);
	}
	st.insert(node{l,r,x});
	for(auto t : vec2){
		st.insert(t);
	}
}
//原先投票意愿为x的投票者的投票意愿变为了w。特别地,若w=0,表示这些投票者暂时找不到新的支持的作品
void XG(int x,int w){
	vector<set<node>::iterator> vec;
	vector<node> vec2;
	for(auto it = st.begin();it!=st.end();it++){
		node t = (*it);
		if(t.v == x){
			if(w){
				//vec2.push_back(node{t.l, t.r, w});
				const_cast<node&> (*it) = node{t.l, t.r, w};
			} 
			else vec.push_back(it);
		} 
	}
	for(auto t : vec){
		st.erase(t);
	}
	for(auto t : vec2){
		st.insert(t);
	}
}
//x的投票者的投票意愿变为了y,互换 
void DD(int x,int y){
	vector<node> vec2;
	vector<set<node>::iterator> vec;
	for(auto it = st.begin();it != st.end();it++){
		node t = (*it);
		if(t.v == x) t.v = y;
		else if(t.v == y) t.v = x;
		if(t.v == x || t.v == y){
			//vec.push_back(it);
			const_cast<node&> (*it) = node{t.l, t.r, t.v};
			//vec2.push_back(node{t.l, t.r, t.v});
		}
	}
	for(auto t : vec){
		st.erase(t);
	}
	for(auto t : vec2){
		st.insert(t);
	}
} 
//调查有多少投票者目前支持作品w,w=0相当于调查有多少投票者目前没有支持的作品。 
void query1(int w){
	int res = 0;
	int ans = n; 
	for(node t : st){
		ans -= t.r - t.l + 1;
		if(t.v == w){
			res += t.r - t.l + 1;
		}
	}
	if(w) cout<<res<<endl;
	else cout<<ans<<endl;
}

//。定得票数至少为1且最多的作品获胜,得票数相同则编号较小的作品获胜。
//特别地,若所有作品均无得票,认为不存在获胜作品。0
void query2(){
	int ans = 0, id = -1;
	unordered_map<int,int> mp;
	for(node t : st){
		if(mp.count(t.v)) mp[t.v] += t.r - t.l + 1;
		else mp[t.v] = t.r - t.l + 1;
		if(mp[t.v] > ans || (mp[t.v] == ans && t.v < id)) ans = mp[t.v], id = t.v; 
	}
	if(id == -1) cout<<0<<endl;
	else cout<<id<<endl; 
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); 
	cout.tie(0);
	cin>>n>>m>>q;
	//num = n;//初始化为都没投票 
	int op;
	while(q--){
		cin>>op;
		if(op == 1){
			int l,r,x;cin>>l>>r>>x;
			LP(l,r,x);
		}
		else if(op == 2){
			int x,w;cin>>x>>w;
			XG(x,w);
		}
		else if(op == 3){
			int x,y;cin>>x>>y;
			DD(x,y);
		}
		else if(op == 4){
			int w;cin>>w;
			query1(w); 
		}
		else{
			query2();
		}
	}
	return 0;
} 

2022-06 CSP

第1题 归一化处理

第2题 寻宝!大冒险!

不难,细节看代码。

#include<bits/stdc++.h>
using namespace std;

int n, L, S;
vector<pair<int,int>> vec;
int B[55][55];
int ans;

int main(){
    cin>>n>>L>>S;
    int x,y;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&x,&y);
        vec.push_back(make_pair(x, y));
    }
    for(int i=S;i>=0;i--){
        for(int j=0;j<=S;j++){
            scanf("%d", &B[i][j]);
        }
    }
    for(int i=0;i<n;i++){
        int x = vec[i].first, y = vec[i].second;
        if(x + S > L || y + S > L) continue;
        int flag = 1;
        for(int ii=S;ii>=0;--ii){
            if(!flag) break;
            for(int jj=0;jj<=S;++jj){
                auto p = find(vec.begin(), vec.end(), make_pair(x+ii, y+jj));
                if(B[ii][jj] && p == vec.end() || !B[ii][jj] && p != vec.end()){
                    flag = 0;
                    break;
                }
            }
        }
        if(flag) ans++;
    }
    cout<<ans<<endl;
    return 0;
}

第3题 角色授权​​​​​​

50分钟调完,优化了几次硬只有70分的超时,我把感觉能优化的地方都优化了,然而并没有什么卵用... 大模拟题你卡时间,CSP你真行啊!

附70超时代码

#include<bits/stdc++.h>
using namespace std;

struct JIAOSE{//角色
    string name;
    unordered_map<string,int> czqd;//操作清单
    unordered_map<string,int> zyzl;//资源种类
    unordered_map<string,int> zymc;//资源名称
};

unordered_map<string,JIAOSE> JS;//角色集合

struct GUANLIAN{//角色关联
    string name;
    unordered_map<string,int> sqdx1;//授权对象(用户)
    unordered_map<string,int> sqdx2;//授权对象(用户组)
}guanlian[505];

int n,m,q;

int main(){
    ios::sync_with_stdio(false);//加快cin的读入速度,但是scanf将会不能用。
    cin.tie(0);
    cin>>n>>m>>q;
    for(int i=1;i<=n;++i){
        JIAOSE jiaose;
        //jiaose.name.resize(10);
        //scanf("%s", &jiaose.name[0]);
        cin>>jiaose.name;
        int nv, no, nn;
        string str;
        //scanf("%d",&nv);
        cin>>nv;
        for(int t=1;t<=nv;++t){
            cin>>str;
            jiaose.czqd[str] = 1;
        }
        //scanf("%d",&no);
        cin>>no;
        for(int t=1;t<=no;++t){
            cin>>str;
            jiaose.zyzl[str] = 1;
        }
        //scanf("%d",&nn);
        cin>>nn;
        for(int t=1;t<=nn;++t){
            cin>>str;
            jiaose.zymc[str] = 1;
        }
        JS[jiaose.name] = jiaose;
    }
    for(int i=1;i<=m;++i){
        //guanlian[i].name.resize(10);
        //scanf("%s",&guanlian[i].name[0]);
        cin>>guanlian[i].name;
        int ns; //scanf("%d", &ns);
        cin>>ns;
        for(int t=1;t<=ns;++t){
            string s1, s2;
            cin>>s1>>s2;
            if(s1 == "u") guanlian[i].sqdx1[s2] = 1;
            else guanlian[i].sqdx2[s2] = 1;
        }
    }
    for(int i=1;i<=q;++i){
        string name; cin>>name;
        int ng;
        cin>>ng;
        unordered_map<string,int> yhz;//用户组
        string s;
        for(int t=1;t<=ng;++t){
            cin>>s;
            yhz[s] = 1;
        }
        string czmc_t, zyzl_t, zymc_t;
        cin>>czmc_t>>zyzl_t>>zymc_t;
        unordered_map<string,int> mp;//维护查询时关联的角色
        for(int t=1;t<=m;++t){
            if(mp.count(guanlian[t].name)) continue;
            if(guanlian[t].sqdx1.count(name)){
                mp[guanlian[t].name] = 1;
                continue;
            }
            for(auto tmp : yhz){
                string sname = tmp.first;
                if(guanlian[t].sqdx2.count(sname)){
                    mp[guanlian[t].name] = 1;
                    break;
                }
            }
        }
        int flag = 0;
        for(auto tmp : mp){
            string sname = tmp.first;
            if(flag) break;
            JIAOSE tj = JS[sname];
            if(!tj.czqd.count(czmc_t) && !tj.czqd.count("*")) continue;
            if(!tj.zyzl.count(zyzl_t) && !tj.zyzl.count("*")) continue;
            if(!tj.zymc.count(zymc_t) && tj.zymc.size()) continue;
            flag = 1;
        }
        if(flag) cout<<1<<endl;
        else cout<<0<<endl;
    }
    return 0;
}

 2022-03 CSP

第1题 未初始化警告

有手就行 略

第2题 出行计划

题意:给定很多区间,每次查询给定一个数,查询位于多少个区间中,前缀和处理即可。

#include<iostream>
using namespace std;

const int maxn = 4e5 + 5;
int n,m,k;
int t[maxn],c[maxn],b[maxn],sum[maxn];

int main(){
	cin >> n >> m >> k;
	int maxx = 0,minn = 9999999;
	for(int i=1;i<=n;i++){
		scanf("%d%d",&t[i],&c[i]);
		minn = min(minn, t[i] - k - c[i] + 1);
		maxx = max(maxx, t[i] - k + 1);
	}
	if(minn < 0) minn = -minn;
	else minn = 0;
	for(int i=1;i<=n;i++){
		b[t[i] - k - c[i] + 1 + minn]++;
		b[t[i] - k + 1 + minn]--;
	}
	sum[0] = b[0];
	for(int i=1;i<=maxx + minn;i++){
		sum[i] = sum[i-1] + b[i];
	}
	int q;
	for(int i=1;i<=m;i++){
		scanf("%d",&q);
		cout << sum[q + minn] << endl;
	}
	return 0;
} 

也可以用树状数组,把每个区间里的位置都加一,然后单点查询每个点有多少个区间覆盖。

#include<iostream>
using namespace std;
#define lowbit(i) ((i)&(-i))
const int maxn = 4e5 + 5;
int n,m,k;
int t[maxn],c[maxn],b[maxn],sum[maxn];
int tree[maxn];

void update(int x, int v){
	for(int i=x;i>0;i-=lowbit(i)){
		tree[i] += v;
	}
}

int getsum(int x){
	int sum = 0;
	for(int i=x;i<maxn;i+=lowbit(i)){
		sum += tree[i];
	}
	return sum;
} 

int main(){
	cin >> n >> m >> k;
	int maxx = 0,minn = 9999999;
	for(int i=1;i<=n;i++){
		scanf("%d%d",&t[i],&c[i]);
		minn = min(minn, t[i] - k - c[i] + 1);
		maxx = max(maxx, t[i] - k + 1);
	}
	if(minn <= 0) minn = -minn + 2;
	else minn = 0; 
	for(int i=1;i<=n;i++){
		//区间修改+1 [t-k-c+1 ~ t-k] 
		update(t[i] - k + minn, 1);
		update(t[i] - k - c[i] + minn, -1);
	}
	int q;
	for(int i=1;i<=m;i++){
		scanf("%d",&q);
		//单点查询 q
		cout << getsum(q + minn) << endl;
		 
	}
	return 0;
} 

第3题 计算资源调度器

慢慢模拟就能AC,主要是细心和耐心,没有考察优化,本来以为要优化查询才会AC,没想到直接AC了,看来主要考察写模拟题的准确度和速度。

#include<bits/stdc++.h>
using namespace std;

int n,m;
//计算结点的 1.所属可用区 2.计算了哪些任务 3.计算任务个数 4.结点编号 
struct node{
	int num,cnt = 0,numm;//1 3 4
	vector<int> job;//2
	friend bool operator < (const node &a, const node &b){
		return (a.cnt == b.cnt) ? a.numm < b.numm : a.cnt < b.cnt; 
	}
}N[1005];
vector<int> kyq[1005];//每个可用区执行了的应用 

void Print(vector<node> vec, int a){
	sort(vec.begin(),vec.end());
	int tmp = vec[0].numm;//选择的结点编号 
	cout << tmp << " ";
	N[tmp].cnt++;
	N[tmp].job.push_back(a);
	kyq[N[tmp].num].push_back(a);
}

void deal(int a,int p1,int p2,int p3,int p3r){
	vector<node> canuse;//可用的结点
	vector<node> backup;//不考虑p3的备用 
	int cnt1 = 0, cnt2 = 0;
	for(int i=1;i<=n;i++){
		if(!(p1 ? N[i].num == p1 : 1)) continue;//指定可用区 
		if(p2){//指定任务亲和 
			bool flag = 0;
			for(int t : kyq[N[i].num])
				if(t == p2){flag = 1; break;}
			if(!flag) continue;
		}
		backup.push_back(N[i]); 
		cnt1++; 
		if(p3){//指定任务不亲和 
			bool flag = 1;
			for(int t : N[i].job)
				if(t == p3){flag = 0; break;}
			if(!flag) continue;
		}
		canuse.push_back(N[i]);
		cnt2++;
	}
	if(!p3 && !cnt1){//没有可用的结点 
		cout << 0 << " ";
		return;
	}
	if(!p3 && cnt1){Print(backup,a); return;}
	if(p3){
		if(!cnt2){
			if(cnt1 && !p3r) Print(backup,a);
			else cout << 0 << " ";
			return;
		}
		else{
			Print(canuse,a); 
		}
	}
}

int main(){
	scanf("%d%d",&n,&m);
	int t;
	for(int i=1;i<=n;i++){
		scanf("%d",&N[i].num);
		N[i].numm = i;
	}
	int g;scanf("%d",&g);
	int f,a,p1,p2,p3,p3r;//个数 应用编号 指定区 指定应用 避免指定应用 尽量 
	while(g--){
		scanf("%d%d%d%d%d%d",&f,&a,&p1,&p2,&p3,&p3r);
		while(f--) deal(a,p1,p2,p3,p3r);
		cout << endl;
	}
	return 0;
}

第4题 通信系统管理

横跨一个周才写,清明放假去玩了哈哈

 写的不是很好,有点冗余。

谈几点总结吧,

1.本题要用到大量stl,用普通数组会爆空间,要用unordered_map(比map查询速度更快),对额度的最大值维护要用到set,也可以用两个priority_queue(因为需要用到删除元素的功能,单独的priority_queue只能删除堆顶);

2.一个申请拆成两个处理时间不同的申请;

3.之前两次TLE的原因是查询时都全部遍历,应该在每次可能改变查询结果的时候更新,查询时直接输出即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5 + 5;
//两台机器的可用额度
unordered_map<int,ll> ed[maxn];
//申请
struct sq{
	int u,v,x;
};
unordered_map<int,vector<sq> > update;
struct pc{
	int num;//编号 
	ll val;//额度 
	friend bool operator<(pc a, pc b){//set中pc的额度降序排列,如果额度相同,编号升序排列,确保ser.begin()为主要对象 
		return a.val == b.val ? a.num < b.num : a.val > b.val;
	}
};
//通信主要对象
set<pc> dx[maxn];//每个pc的最大值 
int n,m,gdcnt,cpcnt,vis[maxn],couple[maxn];

void deal(int time){//处理time当天申请 
	auto tmp = update[time];
	int size = tmp.size(), u,v,x;
	for(int i=0;i<size;i++){
		u = tmp[i].u; v = tmp[i].v; x = tmp[i].x;
		int flag1 = dx[u].size(), flag2 = dx[v].size();//记录是否有通信对象 
		int udx,vdx,udx2,vdx2; 
		udx = flag1 ? (*dx[u].begin()).num : 0;//u原本的主要对象 
		int uiscp = (flag1 && (*dx[udx].begin()).num == u) ? 1 : 0;//u是否属于通信对
		vdx = flag2 ? (*dx[v].begin()).num : 0;//v原本的主要对象 
		int viscp = (flag2 && (*dx[vdx].begin()).num == v) ? 1 : 0;//v是否属于通信对
		if(ed[u][v]) {dx[u].erase(pc{v,ed[u][v]}); dx[v].erase(pc{u,ed[v][u]});}//如果set里面有的话要先删去
		ed[u][v] += x; ed[v][u] += x;
		if(ed[u][v]) {dx[u].insert(pc{v,ed[u][v]}); dx[v].insert(pc{u,ed[v][u]});}//额度为0不会放进set 
		if(flag1 && !dx[u].size()) gdcnt++;//原本有通信对象,更新后没了,孤岛数+1
		if(flag2 && !dx[v].size()) gdcnt++;//原本有通信对象,更新后没了,孤岛数+1
		if(!flag1 && dx[u].size()) gdcnt--;//原本没有通信对象,更新后有了,孤岛数-1
		if(!flag2 && dx[v].size()) gdcnt--;//原本没有通信对象,更新后有了,孤岛数-1
		udx2 = dx[u].size() ? (*dx[u].begin()).num : 0;//u现在的主要对象 
		int uiscp2 = (udx2 && (*dx[udx2].begin()).num == u) ? 1 : 0;//u现在是否属于通信对
		vdx2 = dx[v].size() ? (*dx[v].begin()).num : 0;//v现在的主要对象 
		int viscp2 = (vdx2 && (*dx[vdx2].begin()).num == v) ? 1 : 0;//v现在是否属于通信对
		if(!uiscp && uiscp2) cpcnt++; //u原本不属于pc 现在属于pc   cnt++
		if(uiscp && !uiscp2) cpcnt--;//u原本属于pc   现在不属于pc cnt--
		if(!viscp && viscp2 && vdx2 != u) cpcnt++;//v原本不属于pc 现在属于pc   如果对象不是u cnt++
		if(viscp && !viscp2 && vdx != u) cpcnt--;//v原本属于pc   现在不属于pc 如果原本对象不是u cnt--
		if(viscp && viscp2 && vdx == u && vdx2 != u) cpcnt++;//v原本属于pc且对象是u 现在属于pc对象不是u cnt++ 
		if(viscp && viscp2 && vdx != u && vdx2 == u) cpcnt--;//v原本属于pc且对象不是u 现在属于pc对象是u cnt--  
	}
}
int main(){
	cin >> n >> m;
	gdcnt = n;//孤岛数目初始化为n 
	for(int i=1;i<=m;i++){
		int k,u,v,x,y;scanf("%d",&k);
		while(k--){
			scanf("%d%d%d%d",&u,&v,&x,&y);
			update[i].push_back(sq{u,v,x});
			update[i+y].push_back(sq{u,v,-x});
		}
		deal(i);//处理当天申请
		int l;scanf("%d",&l);//主要对象 
		while(l--){
			int t;scanf("%d",&t);
			if(!dx[t].size()) cout << 0 << endl;
			else {
				cout << (*(dx[t].begin())).num << endl;
			}
		}
		int p,q;scanf("%d%d",&p,&q);
		if(p) cout << gdcnt << endl;//孤岛 
		if(q) cout << cpcnt << endl;//对 
	}
	return 0;
} 

2> 2021-12 CSP

第1题 序列查询

       最开始想到用upper_bound,upper_bound在数组中找到的第一个大于x的下标-1就是数组中最大的小于等于x的下标,但是要遍历N,二分查找复杂度log(n),时间复杂度大 (虽然能AC)

        然后仔细想,区间[a[i-1] ,a[i])的f值都为a[i],所以遍历数组,只需O(n)的复杂度

#include<bits/stdc++.h>
using namespace std;

int n,m;
const int maxn = 1e5 + 5;
int a[maxn],r,g;
long long ans;

int main(){
	cin >> n >> m;
	r = m / (n+1); //g = x / r;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	int i = 1;
	for(;i<=n;i++){
        //区间[a[i-1], a[i]-1 ] f值相同 值为i-1
        ans += (i-1) * (a[i] - a[i-1]);
	}
	//遍历完了数组 边界还在没跑完 (最后一个数在边界上或者左边)
    //f相同区间 [ a[i-1] , m-1 ]  f值为 i - 1
	if(a[i-1] <= m-1){
		ans += (i-1) * (m - a[i-1]);
	}
	cout << ans << endl;
	return 0;
}

第2题 序列查询新解

        同样遍历数组,对于每一个f值相同的区间,检查g值是否相同,为了更快,检查g值相同区间不能挨个遍历,观察式子可以用余数的性质挨个查询g值相同的区间。

#include<bits/stdc++.h>
using namespace std;

int n,m;
const int maxn = 1e5 + 5;
int a[maxn],r,g;
long long ans;

int main(){
	cin >> n >> m;
	r = m / (n+1); //g = x / r;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	int i = 1;
	for(;i<=n;i++){
        //区间[a[i-1], a[i]-1 ] f值相同 值为i-1
        //下面检查这段区间[ a[i-1], a[i]-1 ]里的g值是否等于i-1 
        int pos = a[i-1];
        int gr, cnt;
        while(pos < a[i]){// r - pos % r
            g = pos / r;
            //寻找相同g值的区间
            gr = pos + r - pos % r; // [pos ,  pos + r-pos%r) 的g值相同 但要检查这个区间是否在相同的f区间内
            if(gr <= a[i]){ // 在f相同的区间内 数量为 r - pos%r
                cnt = r - pos % r;
                ans += abs(g - (i-1)) * cnt;
                pos += r - pos % r;
                //cout << "pos = "<< pos << endl;
            }
            else if(gr > a[i]){//超出了f相同的区间 只取到a[i] 数量为a[i] - pos
                cnt = a[i] - pos;
                ans += abs(g - (i-1)) * cnt;
                pos = a[i];
                // cout << "pos = "<< pos << endl;
            }
        }
	}
	//遍历完了数组 边界还在没跑完 (最后一个数在边界上或者左边)
    //f相同区间 [ a[i-1] , m-1 ]  f值为 i - 1
	if(i>n && a[i-1] <= m-1){
		int pos = a[i-1];
        int gr, cnt;
        while(pos < m){
            g = pos / r;
            //寻找相同g值的区间
            gr = pos + r - pos % r; // [pos , pos+pos%r) 的g值相同 但要检查这个区间是否在相同的f区间内
            if(gr <= m){ // 在f相同的区间内 数量为 pos%r
                cnt =r - pos % r;
                ans += abs(g - (i-1)) * cnt;
                pos += r - pos % r;
                 //cout << "pos = "<< pos << endl;
            }
            else if(gr > m){//超出了f相同的区间 只取到m 数量为m - pos
                cnt = m - pos;
                ans += abs(g - (i-1)) * cnt;
                pos = m;
                // cout << "pos = "<< pos << endl;
            }
        }
	}
	cout << ans << endl;
	return 0;
}

第4题 磁盘文件操作

像做模拟题一样,,爆空间只有25分

#include<bits/stdc++.h>
using namespace std;

const int maxn = 2e5 + 5, maxm = 1e7 + 5;
int save[maxm],use[maxn],pre[maxm];//save维护内存里的数字,use维护内存谁在用,pre维护上一次内存是谁占用
int n,m,k;

void write(){//输出一个整数表示此次操作写入成功的最右位置;特别地如果该操作一个位置也没有写入成功,输出 -1
    int id,l,r,x; cin >> id >> l >> r >> x;
    int pos;
    for(pos = l;pos <= r;pos++){
        if(!use[pos] || use[pos] == id){//若目前不被任何程序占用或者被id占用就可以写
            save[pos] = x;
            use[pos] = id;
        }
        else{//一单遇到else 就退出
            break;
        }
    } 
    if(pos != l)cout << pos-1 << endl;
    else cout << -1 << endl;
}

void del(){//该操作成功,输出一个字符串 OK ,否则输出一个字符串 FAIL
    int id,l,r; cin >> id >> l >> r;
    //这一操作当且仅当[l,r]区间内所有位置都正在被程序id占用时才能成功执行
    int flag = 1;
    for(int pos = l;pos <= r;pos++){
        if(use[pos] != id) {flag = 0; break;}
    }
    if(!flag) cout << "FAIL" << endl;
    else{
        for(int pos = l;pos <= r;pos++)
            use[pos] = 0, pre[pos] = id;//记录上一次占用的是谁,值没有被修改
        cout << "OK" << endl;
    }
}

void recover(){//该操作成功,输出一个字符串 OK ,否则输出一个字符串 FAIL
    int id,l,r; cin >> id >> l >> r;
    //这一操作当且仅当[l,r]区间内所有位置都未被占用,且上一次被占用是被程序id占用时才能成功执行
    bool flag = 1;
    for(int pos = l;pos <= r;pos++){
        if(use[pos] || pre[pos] != id) {flag = 0; break;}
    }
    if(!flag) cout << "FAIL" << endl;
    else{
        for(int pos = l;pos <= r;pos++){
            use[pos] = id;
        }
        cout << "OK" << endl;
    }
}

void read(){//返回结果为两个整数 id val
    int pos; cin >> pos;
    if(use[pos]){
        cout << use[pos] << " " << save[pos] << endl;
    }
    else cout << "0 0" << endl;
}

int main(){
    cin>>n>>m>>k;
    int op;
    while(k--){
        cin >> op;
        if(op == 0){write();}
        else if(op == 1){del();}
        else if(op == 2){recover();}
        else if(op == 3){read();}
    }
    return 0;
}

3> 2021-09 CSP

第1题 数组推导

sum_max时,a数组的每个数都取当前b数组的数,也就是b数组本身,求和即可

sum_min时,在b数组出现增加的时候的位置,a数组取b数组当前位置的数,其余位置(未增加)均取0即可

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1e5 + 5;
int n, b[maxn], sum_max, sum_min;

int main()
{
    cin >> n;
    for(int i=1;i<=n;i++){
        scanf("%d",&b[i]);
        sum_max += b[i];
        sum_min += (b[i] > b[i-1]) ? b[i] : 0;
    }
    cout << sum_max << endl << sum_min << endl;
    return 0;
}

第2题 非零段划分

        首先暴力,遍历p(1-10000),对于每个p计算非零段个数,代码如下,时间复杂度e^9,只能得70分

 for(int p=1;p<=10000;p++){
        int cnt = 0;
        for(int i=1;i<=n;i++){
            if(a[i] < p) a[i] = 0;
            if(!a[i-1] && a[i]) cnt++;
        } 
        ans = max(ans, cnt);
    }

        想办法优化,思考如果依然从小到大遍历p,对于每个p当前的非零段个数必须重新统计,这样e^9根本避免不了,得想出一种可以避免每次都要统计非零段个数的方法。

        首先对a数组进行去重,可以发现一段连续相同的值是没有意义的,只用保留一个。

        我们试着从大到小遍历p,对于数组中的每一个a[i],如果它大于周围相邻的两个点,那么它对p等于a[i]时(或者更小的时候)非零段个数的贡献是+1,可以把p的高度想象成海平面,这种情况属于是在海平面露出了一个岛屿,所以非零段加一;而如果它均小于周围相邻的两个点,那么它对p等于a[i]时非零段个数的贡献是-1,这种情况在海平面是p的时候将左右两个本来没有连接的岛屿(海平面从上往下看)连接了起来形成了一个岛屿,因此非零段个数减一;其余两种情况,a[i]小于左边a[i-1]并且大于a[i+1] 或者 a[i]大于左边a[i-1]并且小于a[i+1] ,这两种情况对于答案的贡献是0,也就是没有贡献,对于海平面是p的时候,只是把岛屿延展了一个点,并没有增加个数。

        我们用cnt[i]数组维护海平面为i时,每个点对答案的贡献之和。那么对于每个点a[i],要更新它在海平面为a[i]时对答案的贡献,也就是更新cnt[a[i]];

        最后我们模拟海平面从最高处(10000)开始往下降,对于每个高度p,计算cnt[10000 ~ 当前p]的和(也就是cnt的后缀和)即是当前海平面p的非零段个数。找到最大值即为答案

#include<bits/stdc++.h>
using namespace std;

int n,m;
const int maxn = 5e5 + 5;
int a[maxn],cnt[10005];

int main(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> a[i];
	n = unique(a + 1, a + 1 + n) - (a + 1);//unique返回的是不重复元素的下一个元素的位置
	a[n+1] = 0;//n后面的值unique函数是没有删除原有元素的,所以后一个位置要赋为0
	for(int i=1;i<=n;i++){
		int x = a[i-1], y = a[i], z = a[i+1];
		if(x < y && y > z) cnt[y]++; //对于a[i]海平面高度时,贡献加一
		if(x > y && y < z) cnt[y]--; //对于a[i]海平面高度时,贡献减一
	}
	int sum = 0, ans = 0;
	for(int i=10000;i>=0;i--){ //海平面从高到低下降 统计非零段个数 寻找最大值
		sum += cnt[i];
		ans = max(ans, sum);
	}
	cout << ans << endl;
	return 0;
}

第3题 脉冲神经网络

大模拟,写了接近2个小时,最后还是66分的TLE,终究是我太菜了,这道题也很坑卡常数,我看网上有讲怎么卡过去,但是我懒得再去改了。。。如果是在考场上66分就66分吧~

(以下是TLE66分代码,蒟蒻投降)

#include<bits/stdc++.h>
using namespace std;
static unsigned long nxt = 1;

/* RAND_MAX assumed to be 32767 */
int myrand(void) {
    nxt = nxt * 1103515245 + 12345;
    return((unsigned)(nxt/65536) % 32768);
}

int N,S,P,T;//有N个神经元,S个突触和P个脉冲源,输出时间刻T时神经元的v值。
double dt;
//突触tc
struct tc{
	int s,t;
	double w;
	int D;
	vector<int> time;//该突触发放脉冲的时间
}t[1010];
//神经元sjy
struct sjy{
	double v,u,a,b,c,d;
	double I;//脉冲强度
	int cnt;//发放脉冲次数
}s[1010];
//脉冲源mcy
struct mcy{
	double r;
}m[2020];

vector<int> son[2020];//sjy和mcy的后继
vector<int> fa[1010];//sjy的父亲

int main(){
	//输入
	cin>>N>>S>>P>>T>>dt;
	int num = 0;//需要输入N个 
	while(num<N){//输入神经元[0,N-1]
		int rn;scanf("%d",&rn);
		double v,u,a,b,c,d;scanf("%lf%lf%lf%lf%lf%lf",&v,&u,&a,&b,&c,&d);
		while(rn--){
			s[num].v = v,s[num].u = u,s[num].a = a,s[num].b = b,s[num].c = c,s[num].d = d;
			num++;
		}
	}
	for(int i=N;i<=N+P-1;i++){//输入脉冲源[N,N+P-1]
		scanf("%lf",&m[i].r);
	}
	for(int i=0;i<S;i++){//输入突触[0,S-1]
		scanf("%d%d%lf%d",&t[i].s,&t[i].t,&t[i].w,&t[i].D);
		son[t[i].s].push_back(i);
		fa[t[i].t].push_back(i);
	}

	//开始模拟
	for(int now=1;now<=T;now++){
		//1.模拟神经元
		for(int i=0;i<N;i++){
			double v0 = s[i].v, I = 0;//I为此刻受到的脉冲强度
			//更新此刻到达该神经元的I/
			for(unsigned int j=0;j<fa[i].size();j++){//遍历该sjy突触父亲
				if(!t[fa[i][j]].time.empty() && t[fa[i][j]].time[0] == now){//如果该突触是连接到当前神经元并且发放时间是此刻
					I += t[fa[i][j]].w; t[fa[i][j]].time.erase(t[fa[i][j]].time.begin());
				}
			}
			s[i].v = v0 + dt*(0.04*v0*v0 + 5*v0 + 140 - s[i].u) + I;
			s[i].u = s[i].u + dt*s[i].a*(s[i].b*v0 - s[i].u);
			//判断是否发放脉冲
			if(s[i].v >= 30){
				for(unsigned int j=0;j<son[i].size();j++){
					t[son[i][j]].time.push_back(now+t[son[i][j]].D);//增加发放脉冲
				}
				//改变值
				s[i].v = s[i].c;
				s[i].u += s[i].d;
				s[i].cnt++;
			}
		}
		//2.模拟脉冲源
		for(int i=N;i<N+P;i++){
			if(m[i].r > myrand()){//该脉冲源达到发放条件
				//遍历突触发放
				for(unsigned int j=0;j<son[i].size();j++)
				t[son[i][j]].time.push_back(now+t[son[i][j]].D);
			}
		}
		//输出每个时刻的v值
		// for(int i=0;i<N;i++){
		// 	cout << "时刻" << now << ": " << s[i].v << endl;
		// }
	}
	//3.统计T时刻最小最大v和最小最大cnt
		double ans1 = INT64_MAX, ans2 = INT64_MIN;
		int    ans3 = INT32_MAX, ans4 = INT32_MIN;
		for(int i=0;i<N;i++){
			if(s[i].v < ans1) ans1 = s[i].v;
			if(s[i].v > ans2) ans2 = s[i].v;
			if(s[i].cnt < ans3) ans3 = s[i].cnt;
			if(s[i].cnt > ans4) ans4 = s[i].cnt;
		} 
		//输出
		printf("%.3lf %.3lf\n",ans1,ans2);
		printf("%d %d\n",ans3,ans4);
	return 0;
}

第4题 收集卡牌

        一看题,高兴地妥妥的dfs,暴力dfs代码如下

#include<bits/stdc++.h>
using namespace std;

int n,k,vis[20];
double p[20],ans;

void dfs(int card, double cnt, double fre){//凑到了card张牌,抽卡次数,目前的概率前缀fre
	if((card == n) || ((cnt - card) / k >= n - card)){//凑齐了牌 //用硬币可以凑齐剩下的牌
	  	ans += cnt * fre;
		return;
	}
	//没有凑齐的话,继续抽卡
	//cnt++;         //抽卡次数加1
	for(int i=1;i<=n;i++){
		//fre *= p[i]; //凑到第i张的概率 
		if(vis[i]){//已经抽到了第i张
			dfs(card, cnt+1, fre*p[i]);
		}
		else{       //抽到了新的一张牌
			vis[i] = 1;
			dfs(card+1, cnt+1, fre*p[i]);
		    //回溯
			vis[i] = 0;
		}
	}
}

int main(){
	cin >> n >> k;
	for(int i=1;i<=n;i++){
		scanf("%lf",&p[i]);
		//cin >> p[i];
	}
	dfs(0, 0, 1);
	//cout << ans << endl;
	printf("%.10f\n",ans);
	return 0;
}

样例全过,以为至少可以浅得20分,结果最开始0分,上网搜了下原因说这题输出精度要十位小数才行,好吧,我就改成十位小数输出,这下总可以得个20了吧,结果只有10分而且是WA。。

调了很久才发现是官方数据应该是后缀计算的概率,而我的dfs是前缀计算的概率,计算过程中超过了浮点数的范围使得最后答案和后缀计算的有偏差,比如下面这个例子

ans1 = 0.1212 * 0.1234 * 0.0774 * 0.2336 * 0.08682 * 0.05627 * 0.1197 * 0.0488 * 0.1223;
ans2 = 0.1223 * 0.0488 * 0.1197 * 0.05672 * 0.08682 * 0.2336 * 0.0774 * 0.1234 * 0.1212;
printf("ans1 = %.10f\n",ans1);
printf("ans2 = %.10f\n",ans2);

输出: 

ans1 = 0.0000000009
ans2 = 0.0000000010

美美重新写一个前缀计算概率的dfs,拿到20分TLE,道阻且长。。。

#include<bits/stdc++.h>
using namespace std;

int n,k,vis[20];
double p[20];

double dfs(int card, double cnt){//凑到了card张牌,抽卡次数
	if((card == n) || ((cnt - card) / k >= n - card)){//凑齐了牌 //用硬币可以凑齐剩下的牌
		return cnt;
	}
	double ans = 0;
	for(int i=1;i<=n;i++){
		if(vis[i]){//已经抽到了第i张,coin加一
			ans += p[i]*dfs(card, cnt+1);
		}
		else{       //抽到了新的一张牌
			vis[i] = 1;
			ans += p[i]*dfs(card+1, cnt+1);
			vis[i] = 0;
		}
	}
	return ans;
}

int main(){
	cin >> n >> k;
	for(int i=1;i<=n;i++){
		scanf("%lf",&p[i]);
		//cin >> p[i];
	}
	double ans = dfs(0, 0);
	printf("%.10f\n",ans);
	return 0;
}

一共最多有16张牌,并且每张牌有选或者不选两种可能,可以联想到状压dp,思路如下:

首先设置抽0次达到0这个状态的概率为1(必然发生)
然后抽取次数从1次开始到1+(n-1)*k次(最多次数是一直抽到同一张)遍历
对于每次抽取的次数再遍历抽取得到的状态j
对于每个状态遍历第i次抽到的卡牌k
检查如果j状态包含了第k张抽到的话,计算抽i次到达j状态期望,有可能是由 抽i-1次就抽到了包含k的j状态或者抽i-1次抽到了j除开第k位的状态 转移到当前情况(当然有可能会是0,此时代表抽i次到不了目前是j状态的情况)
遍历完本次抽到的卡牌后检查加上重复抽卡得到的硬币是否已经抽完,如果抽完则要更新答案,并且把抽i次到j状态的概率设为0,因为不设为0的话后面还会继续用到该值增加答案。
 

#include<bits/stdc++.h>
using namespace std;

int const maxn = 20, cmax = 1+(16-1)*5;//cmax为最多抽的次数
int n,k,cnt[1<<16]; //cnt[i]表示i状态有多少个1
double p[maxn],f[cmax+1][1<<16];
double ans;

int main(){
    cin >> n >> k;
    for(int i=1;i<=n;i++){
        scanf("%lf",&p[i]);
    }
    //初始化状态
    int S = (1 << n) - 1;//状态个数
    for(int i=1;i<=S;i++){//初始化cnt
        int tmp = i;
        while(tmp){
            if(tmp & 1){ cnt[i]++;}
            tmp >>= 1;
        }
    }
    f[0][0] = 1.0000000000;//初始化抽0次到达状态0的概率
    for(int i=1;i<=1+(n-1)*k;i++){//遍历抽卡次数
        for(int j=1;j<=S;j++){//遍历状态
            for(int k=1;k<=n;k++){//遍历第i次抽到的卡
                if(j & (1<<(k-1))){//状态j包含了此次的第k张
                    f[i][j] += (f[i-1][j] + f[i-1][j-(1<<(k-1))]) * p[k];//计算概率
                }
            }
            //检查抽第i次到达j状态时是否已经抽完
            if(cnt[j] + (i-cnt[j])/k == n){
                ans += f[i][j] * i;
                f[i][j] = 0;
            }
        }
    }
    printf("%.10lf\n",ans);
    return 0;
}

4> 2021-04 CSP

第1题 灰度直方图

        有手就行……

#include<bits/stdc++.h>
using namespace std;

int n,m,L,a[505][505],p[260];

int main(){
	cin >> n >> m >> L;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			scanf("%d",&a[i][j]);
            p[a[i][j]]++;
		}
	}
	for(int i=0;i<L;i++) cout << p[i] << " ";
	return 0;
}

第2题 领域均值

        也比较简单,稍微优化一点就行,首先先写出暴力代码,如下,70分

#include<bits/stdc++.h>
using namespace std;

int n,L,r,t,a[605][605],ans;

double calculate(int i, int j){//计算领域的平均值 
//i - r <= x <= i + r  && j - r <= y <= j + r
    double sum = 0, cnt = 0;
    for(int ii = ((i-r)<0 ? 0 : i-r); ii <= ((i+r)>=n ? n-1 : i+r);ii++){
        for(int jj = ((j-r)<0 ? 0 : j-r); jj <= ((j+r)>=n ? n-1 : j+r);jj++){
            sum += a[ii][jj];
            cnt++;
        }
    }
	return sum / cnt;
}

int main(){
	cin >> n >> L >> r >> t;
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			scanf("%d",&a[i][j]);
		}
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if(calculate(i,j) <= t*1.0)  ans++;
		}
	}
	cout << ans << endl;
	return 0;
}

暴力时间复杂度 n*n*r*r -> e^9 ,主要是查询均值的r^2时间花的多,因此主要优化calculate函数

每次计算都是重新计算邻域所有的点,可以考虑从上一次的邻域区域移动到当前区域只有两列发生了变化(没有换行的时候),上一次的最左边一列不再计算,这一次的最右边一列要新加上;这样每行只有第一次需要全部计算,剩下的区域移动后都只用重新计算两列,细节见代码。

#include<bits/stdc++.h>
using namespace std;

int n,L,r,t,a[605][605],ans;
double sum = 0, cnt = 0;

double calculate(int i, int j){//计算领域的平均值
	if(j == 0){//每一行用第一个邻域初始化sum 和 cnt
		sum = cnt = 0;
		for(int ii = ((i-r)<0 ? 0 : i-r); ii <= ((i+r)>=n ? n-1 : i+r);ii++){
			for(int jj = ((j-r)<0 ? 0 : j-r); jj <= ((j+r)>=n ? n-1 : j+r);jj++){
				sum += a[ii][jj];
				cnt++;
			}
		}
		return sum / cnt;
	}
	//邻域向右移动一个单位  sum减去最左边一列 加上右边一列
	if(j - r - 1 >= 0){ //左边这列存在的话要减去
		for(int ii = ((i-r)<0 ? 0 : i-r); ii <= ((i+r)>=n ? n-1 : i+r);ii++){ //这一列
			sum -= a[ii][j-r-1];
			cnt--;
		}
	}
	if(j + r < n){ //右边这列存在的话要加上
		for(int ii = ((i-r)<0 ? 0 : i-r); ii <= ((i+r)>=n ? n-1 : i+r);ii++){ //这一列
			sum += a[ii][j+r];
			cnt++;
		}
	}
	return sum / cnt;
}

int main(){
	cin >> n >> L >> r >> t;
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			scanf("%d",&a[i][j]);
		}
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if(calculate(i,j) <= t*1.0)  ans++;
		}
	}
	cout << ans << endl;
	return 0;
}

第3题 DHCP服务器

一做模拟题就头疼,像做一篇超长的阅读理解然后换个语言复述。。。

跑了70分,我也懒得找细节哪里错了,随缘吧(狗头)

#include<bits/stdc++.h>
using namespace std;

int N;
long long TD,TMAX,TMIN;
string H;
int add1[10005],add2[10005],add3[10005],add4[10005];//地址的状态有未分配、待分配、占用、过期四种。
string user[10005];//地址的占用者
int ever[10005];//是否被占用过
long long expT[10005];//内存的过期时间

void DIS(long long time, string a, string b, int IP, long long exp){
    //检查是否有一个IP 地址 它的占用者为发送主机
    int flag0 = 0,flag1 = 0,flag2 = 0;//记录有无
    int IP1,IP2;//最小的状态为未分配的 IP地址,最小的状态为过期的 IP地址
    for(int i=1;i<=N;i++){
        if(user[i] == a){IP = i;flag0 = 1;break;}//若有,则选取该 IP 地址
        if(!flag1 && add1[i]){IP1 = i; flag1 = 1;}//若没有,则选取最小的状态为未分配的 IP 地址
        if(!flag2 && add4[i]){IP2 = i; flag2 = 1;}//若没有,则选取最小的状态为过期的 IP 地址
    }
    if(flag0 == 0){//没有找到占用者是发送主机的
        if(flag1) IP = IP1;
        else if(flag2) IP = IP2;
        else return; //若没有,则不处理该报文,处理结束;
    }
    //将该 IP 地址状态设置为待分配,占用者设置为发送主机
    add2[IP] = 1; add1[IP] = 0; add4[IP] = 0; add3[IP] = 0;//这里question 占用没有改动
    user[IP] = a;
    //若报文中过期时刻为0 ,则设置过期时刻为t+TD;
    //否则根据报文中的过期时刻和收到报文的时刻计算过期时间,判断是否超过上下限:
    //若没有超过,则设置过期时刻为报文中的过期时刻;否则则根据超限情况设置为允许的最早或最晚的过期时刻;
    if(!exp) exp = 1ll*(time + TD);
    else{
        long long tmp = exp - time;
        //if(tmp >= TMIN && tmp <= TMAX) exp = tmp;
        if(tmp < TMIN) {exp = time + TMIN;}
        else if(tmp > TMAX) exp = time + TMAX;
    }
    expT[IP] = exp;
    //向发送主机发送 Offer 报文,其中,IP 地址为选定的 IP 地址,过期时刻为所设定的过期时刻
    cout << H <<" "<< a <<" OFR "<<IP<<" "<<exp<<endl; 

}

void REQ(long long time, string a, string b, int IP, long long exp){
    //检查接收主机是否为本机:
    if(b != H){//若不是,则找到占用者为发送主机的所有 IP 地址,//对于其中状态为待分配的,将其状态设置为未分配,并清空其占用者,清零其过期时刻,处理结束;
        for(int i=1;i<=N;i++){
            if(add2[i]){add1[i] = 1; add2[i] = 0; user[i] = ""; add3[i] = 0; add4[i] = 0; expT[i] = 0;}
        }
        return;
    }
    //检查报文中的 IP 地址是否在地址池内,且其占用者为发送主机,若不是,则向发送主机发送 Nak 报文,处理结束
    if(IP<1 || IP>N || user[IP]!=a) {cout << H <<" "<< a <<" NAK "<<IP<<" 0"<<endl; return;}
    //无论该 IP 地址的状态为何,将该 IP 地址的状态设置为占用;与 Discover 报文相同的方法,设置 IP 地址的过期时刻
    add3[IP] = 1; add1[IP] = 0; add2[IP] = 0; add4[IP] = 0;
    user[IP] = a;
    if(!exp) exp = 1ll*(time + TD);
    else{
        long long tmp = exp - time;
        //if(tmp >= TMIN && tmp <= TMAX) exp = tmp;
        if(tmp < TMIN) {exp = time + TMIN;}
        else if(tmp > TMAX) exp = time + TMAX;
    }
    expT[IP] = exp;
    //向发送主机发送 Ack 报文
    cout << H <<" "<< a <<" ACK "<<IP<<" "<<exp<<endl; 
}

void update(long long now){//更新当前时间各储存地址的状态
    //处于待分配和占用状态的 IP 地址拥有一个大于零的过期时刻。
    //在到达该过期时刻时,若该地址的状态是待分配,则该地址的状态会自动变为未分配,且占用者清空,过期时刻清零;
    //否则该地址的状态会由占用自动变为过期,且过期时刻清零。
    //处于未分配和过期状态的 IP 地址过期时刻为零,即没有过期时刻。
    for(int i=1;i<=N;i++){
        if(expT[i] > 0 && expT[i] <= now){
            if(add2[i]){add1[i] = 1; add2[i] = 0; user[i] = ""; add3[i] = 0; add4[i] = 0; expT[i] = 0;}
            else{
                add3[i] = 0; add4[i] = 1; add1[i] = 0; expT[i] = 0;
            }
        }
    }
}

int main(){
    cin>>N>>TD>>TMAX>>TMIN>>H;
    //首先初始化 IP 地址池,将所有地址设置状态为未分配,占用者为空,并清零过期时刻。
    for(int i=1;i<=N;i++){add1[i] = 1;user[i] = "";}
    int n;cin>>n;
    int time,IP,exp;
    string a,b,type;
    for(int i=1;i<=n;i++){
        //scanf("%d%s%s%s%d%d",&time,a,b,type,&IP,&exp);
        cin >> time >> a >> b >> type >> IP >> exp;
        //检查是否处理
        bool flag = (b != H) && (b[0]!='*');
        //cout << b << "  "<< H <<endl;
        if(flag){if(type != "REQ") continue;}
        if(b[0] == '*' && type != "DIS") continue;
        if(b == H && type == "DIS") continue;
        update(1ll*time);
        if(type == "DIS") DIS(1ll*time, a, b, IP, 1ll*exp);
        else if(type == "REQ") REQ(1ll*time, a, b, IP, 1ll*exp);
    }
    return 0;
}

第4题 校门外的树

暴力30分,四道题刚好300分,在考场上我绝对就跑路了哈哈...  然后想过dp,我一直觉得每段的方差一定会占一个维度,然后就始终都写不出来转移方程,看了大佬的思路才恍然大悟。

先摆暴力代码吧。

#include<bits/stdc++.h>
using namespace std;

int a[1005],n;
int b[1000005];
long long f[1005][1005];
bool check(int l,int r,int d){
    int pos = l + d;
    while(pos < r){
        if(b[pos] == 1) return false;
        pos += d;
    }
    if(pos == r)return true;
    return false;
}
long long ans;
long long cal(int l, int r, int k){//把区间分成k段,把每段相乘
    if(k == 1) return f[l][r];
    long long cnt = 0;
    for(int p=2;p<n;p++){
        cnt += f[l][p]*cal(p, r, k-1);
    }
    return cnt;
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++) {scanf("%d",&a[i]); b[a[i]] = 1;}
    //暴力
    for(int i=1;i<n;i++){//左端点
        for(int j=i+1;j<=n;j++){//右端点
            int l = a[i], r = a[j];
            int dmax = (r - l) / 2;
            long long cnt = 0;
            for(int d=1;d<=dmax;d++){//遍历公差是否可行
                if(check(l, r, d)) cnt++;
            } 
            f[i][j] = cnt;
        }
    }
    for(int k=1;k<n;k++){//分成多少段 需要划k-1个分割线 不能画在1和n
        ans += cal(1, n, k);
    }
    cout << ans <<endl;
    return 0;
}

dp思路:

在不知道维度设几维时,先设为一维,再看是否需要加维度

先用f[i]表示前i个障碍物的区间方案总数,试着通过前面的状态来寻求转移

f[i]可以由f[i-1]*( [i-1,i)这一段的方案数)构成,不过单用i-1这一个状态就够了吗?肯定是不够的

仔细一想,还得加上[i-2,i)这一段的方案数*f[i-2] 以及还得加下去...

那么转移方程呼之欲出:f[i] = f[i-1]*cnt[i-1][i] + f[i-2]*cnt[i-2][i] +...+ f[1]*cnt[1][i]

递归边界:f[1] = 1  递归终点 f[n]

现在还得考虑很多细节,第一个cnt的处理,cnt[i][j]表示把[i,j)看做一整段能够划分出的等差序列个数,可以考虑j-i的约数个数,因为不能插在边界,所以是j-i约数不包含本身的个数,那么我们只需用cnt维护所有数除本身的约数个数即可

如何计算得到一个数的约数个数呢?可以进行构造,本题需要的最大可能的数是100005,设为M,1-M的约数范围也就是[1,M],我们就可以用1-M作为约数能构造出哪些就可以了,详细实现过程见代码。

第二个细节就是需要约束约数不能在障碍物上插,我们可以在从[i-1,i)区间段到前面扩展的同时,令约数不能使用a[i]-a[i-1]这就保证了再以后的约数选择中不会再选到会直接插到障碍上的约数。不过这还不够,需要考虑同一个约数能不能反复被使用来计算,答案是不能的,比如在某一段区间用过的约数,如果在后面的大区间中还选择这个约数,是肯定会插在上一个用过这个约数区间的左端点的障碍物上,因此同一个约数也不能重复使用。

想好所有的约束条件后,就有了下面的代码。

#include<bits/stdc++.h>
using namespace std;
const int M = 1e5 + 10, mod = 1e9+7;
int a[1005],n,vis[M];//vis维护约数是否使用过
long long f[1005];
vector<int> cnt[M];//cnt[i]维护i的约数有哪些,不包含本身


int main(){
    cin>>n;
    for(int i=1;i<=n;i++) {scanf("%d",&a[i]);}
    //处理cnt
	for(int i=1;i<=M;i++){//i作为约数能构成的数
		for(int j=i*2;j<=M;j+=i){//j个i构成的数 
			cnt[j].push_back(i);
		}
	}
	f[1] = 1;//递归边界 第一个障碍物作为右端点
	for(int i=2;i<=n;i++){
		memset(vis, 0, sizeof vis);
		for(int j=i-1;j>=1;j--){
			//需要检查cnt里的值是否被重复使用,重复使用会插在目前区间的左端点上
			int num = 0;//没被使用过的约数个数
			for(auto cc : cnt[a[i]-a[j]]){
				if(vis[cc]) continue;
				vis[cc] = 1;
				num++;
			}
			vis[a[i]-a[j]] = 1;//防止后面的区间选到目前区间的长度,直接插在目前区间的左端点
			f[i] += (f[j] * num) % mod;
		}
	}
	cout << f[n] << endl;
    return 0;
}

5> 2020-12 CSP

第1题 期末预测之安全指数 和 第2题 期末预测之最佳阈值

第一题懒得写了,下面直接说第二题的优化满分思路。

1.用map存储每个期望y对应的result,并且对于相同的期望y的result可以进行抵消,因为当期望相同,且有多个不同的res时,一旦期望与seta的关系确定,一对res为0和1的可以抵消掉正确预测次数,一对res为1和1可以叠加正确预测次数。

(需要用到map的遍历)

2.将seta初始化为第一个期望,并初始化正确预测次数cnt

3.对于剩下的所有期望按照顺序赋予seta,然后检查cnt。cnt的检查:因为数据对于期望已经去重,所以此时的seta一定比上次的期望大,需要将上一次期望对应的cnt贡献进行更新。比如上一次期望对应的result为-2那么本轮的cnt也需要-2;result为0则cnt不变;result为正数则要把cnt减去result;而其余期望的cnt的贡献是没有改变的,不用检查其他期望节省时间

4.每轮更新cnt之后要检查是否为大于等于目前最大的正确次数,如果是要更新cnt和目前的最佳seta。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 5;
//int y[maxn],res[maxn];//期望和结果
map<int,int> mp;

int main(){
    int n; cin >> n;
    int y,r;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&y,&r);
        if(r) mp[y]++;
        else mp[y]--;
    }
    int seta = mp.begin()->first; //初始seta
    int cnt = 0, cnt_max;
    for(auto tmp : mp){//初始化cnt
        if((tmp.first >= seta) == (tmp.second>0)) cnt += tmp.second;
        else if((tmp.first >= seta) == (tmp.second<0)) cnt -= tmp.second;
    }
    cnt_max = cnt;//初始化最大cnt
    for(auto pos = ++mp.begin();pos != mp.end();pos++){//寻找最佳seta
        //cout << "此时遍历到了期望 = "<< pos->first << " 静结果 = " << pos->second;
        pos--;
        cnt = cnt - (pos->second);
        //cout << "此时正确次数cnt = " << cnt <<endl;
        pos++;
        if(cnt >= cnt_max){
            seta = pos->first;
            cnt_max = cnt;
        }
    }
    cout << seta << endl;
    return 0;
}

5> 2020-06 CSP

第1题 线性分类器 

在ACwing上AC了,但是在官网上居然爆0,运行错误,找了半天我也找不到在哪里...

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e8+20;
int n,m;
struct node{
	int x,y;
	char type;
}N[maxn];

bool check(int s0,int s1,int s2){
	char up = '1', down = '1';
	long long t;
	for(int i=1;i<=n;i++){
		t = s0 + 1ll*s1*N[i].x + 1LL*s2*N[i].y;
		if(t == 0) return false;
		else if(t > 0){
			if(up == '1') up = N[i].type;
			else if(up != N[i].type) return false;
		}
		else if(t < 0){
			if(down == '1') down = N[i].type;
			else if(down != N[i].type) return false;
		}
	}
	return true;
}

int main(){
    cin >> n >> m;
	for(int i=1;i<=n;i++){
		//scanf("%d%d",&N[i].x,&N[i].y);
		cin >> N[i].x >> N[i].y >> N[i].type;
	}
	int s0,s1,s2;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&s0,&s1,&s2);
		if(check(s0,s1,s2)) cout << "Yes" << endl;
		else cout << "No" << endl; 
	}
    return 0;
}

 第2题 稀疏向量

使用双指针很容易解决

#include<bits/stdc++.h>
using namespace std;

const int maxn = 5e5 + 10;
int n,a,b,index[maxn],v[maxn];
long long ans;
struct node{
    int index,val;
}aa[maxn],bb[maxn];

bool cmp(node a,node b){
    return a.index < b.index;
}

int main(){
    cin >> n >> a >> b;
    for(int i=1;i<=a;i++){
        scanf("%d%d",&aa[i].index,&aa[i].val);
    }
    for(int i=1;i<=b;i++){
        scanf("%d%d",&bb[i].index,&bb[i].val);
    }
    sort(aa+1, aa+1+a, cmp);
    sort(bb+1, bb+1+b, cmp);
    int p1=1,p2=1;
    while(p1<=a && p2<=b){
        if(aa[p1].index == bb[p2].index){
            ans += aa[p1].val * bb[p2].val;
            p1++; p2++;
        }
        else if(aa[p1].index < bb[p2].index){
            p1++;
        }
        else if(aa[p1].index > bb[p2].index){
            p2++;
        }
    }
    cout << ans << endl;
    return 0;
}

6> 2019-01 CSP

第1题 报数

#include<bits/stdc++.h>
using namespace std;

int cnt[4],n;

bool check(int num){
	if(num % 7 == 0) return true;
	while(num > 0){
		if(num % 10 == 7) return true;
		num = num / 10;
	}
	return false;
}

int main(){
	cin >> n;
	int num = 1,now = 0, i = 0;//num目前的数,now喊了多少个数,i该谁
	while(now < n){
		//cout << "当前的数是 "<<num<<endl;
		if(check(num)) {cnt[i]++;}//如果是
		else now++;
		num++;
		i = (i + 1) % 4;
	}
	for(int i=0;i<4;i++){
		cout << cnt[i] << endl;
	}
    return 0;
}

第2题 回收站选址

我直接用二分写的,然后我试了下暴力也直接AC了,不禁感叹以前的CSP是真友好

#include<bits/stdc++.h>
using namespace std;

int cnt[5],n;
struct node{
	int x,y;
}N[1005];

bool cmp(node a, node b){
	return (a.x == b.x) ? a.y < b.y : a.x < b.x;
}

bool check(int x,int y){//检查是否存在(x,y)
	int l = 1, r = n;
	while(l < r){
		int mid = (l + r) >> 1;
		if(N[mid].x > x) r = mid - 1;
		else if(N[mid].x < x) l = mid + 1;
		else{l = mid; break;}
	}
	if(N[l].x != x) return false;
    if(N[l].y == y) return true;
	//寻找其他邻近点
	if(N[l].y < y){//检查右边
		while(N[l].x == x){
			if(N[l].y == y) return true;
			l++; 
			if(l > 1001) return false;
		}
		return false;
	}
	else{
		while(N[l].x == x){
			if(N[l].y == y) return true;
			l--; 
			if(l < 1) return false;
		}
		return false;
	}
}

int main(){
	cin >> n;
	for(int i=1;i<=n;i++){
		scanf("%d%d",&N[i].x,&N[i].y);
	}
	sort(N+1, N+1+n, cmp);
	//暴力检查
	for(int i=1;i<=n;i++){
		int xx = N[i].x, yy = N[i].y;
		//检查邻居有没有,有四个才能评分
	    if(check(xx-1, yy) && check(xx+1, yy) && check(xx, yy+1) && check(xx, yy-1)){
			int s = 0;
			if(check(xx+1, yy+1)) s++;
			if(check(xx+1, yy-1)) s++;
			if(check(xx-1, yy+1)) s++;
			if(check(xx-1, yy-1)) s++;
			cnt[s]++;
		}
	}
	for(int i=0;i<5;i++){
		cout << cnt[i] << endl;
	}
    return 0;
}

7> 2019-03 CSP

第2题 二十四点

我用的栈模拟的,写得其实比较乱

#include<bits/stdc++.h>
using namespace std;

char s[10];
int n;
stack<int> num;
stack<char> op;

int main(){
	cin >> n;
	for(int i=1;i<=n;i++){
		scanf("%s",s);
		int res = 0;
		for(int i=0;i<7;i++){
			if(i%2==0){
				int t = s[i] - '0';
				num.push(t);//偶数放在数栈
			}
			else{
				op.push(s[i]);//奇数放在op
				if(op.top() == 'x' || op.top() == '/'){
					int a = num.top(); num.pop();//把上一个操作数拿出来
					int b = s[i+1] - '0';//下一个操作数
					char opp = op.top(); op.pop();
					if(opp == 'x') a = a * b;
					else a = a / b;
					num.push(a);
					i++;
				}
			}
		}
		while(num.size() != 1){
			int b = num.top(); num.pop();
			char opp = op.top(); op.pop();
			int a = num.top(); num.pop();
			if(!op.empty()){
				char opp1 = op.top(); 
				if(opp1 == '-'){
					op.pop();
					op.push('+');
	                a = -a;
				}
			}
		    if(opp == '+') {num.push(a+b);}
			else { num.push(a-b);}
		}
		res = num.top(); num.pop();
		if(res == 24) cout << "Yes" << endl;
		else cout << "No" << endl;
	}
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sean_Chen_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值