├── CNAME
├── Procfile
├── db
├── create_db.sql
├── config.js
├── setup.js
└── init.sql
├── .env
├── package.json
├── problemSets.html
├── about.html
├── services
└── userService.js
├── leaderboard.html
├── server.js
├── userlist.html
├── styles.css
├── auth.js
├── problems.js
├── index.html
└── problem.html
/CNAME:
--------------------------------------------------------------------------------
1 | etoi.us.kg
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm start
2 | release: node db/setup.js
3 |
--------------------------------------------------------------------------------
/db/create_db.sql:
--------------------------------------------------------------------------------
1 | -- 创建数据库
2 | CREATE DATABASE oj_db;
3 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgresql://postgres:voxEVCesYbJwoZByqxRIcabXSdizeKHJ@postgres.railway.internal:5432/railway
2 |
--------------------------------------------------------------------------------
/db/config.js:
--------------------------------------------------------------------------------
1 | const { Pool } = require('pg');
2 | require('dotenv').config();
3 |
4 | const pool = new Pool({
5 | connectionString: process.env.DATABASE_URL,
6 | ssl: {
7 | rejectUnauthorized: false
8 | }
9 | });
10 |
11 | module.exports = {
12 | query: (text, params) => pool.query(text, params),
13 | pool
14 | };
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "oj-system",
3 | "version": "1.0.0",
4 | "description": "Online Judge System",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "node server.js",
8 | "dev": "nodemon server.js",
9 | "setup": "node db/setup.js"
10 | },
11 | "dependencies": {
12 | "bcrypt": "^5.1.1",
13 | "cors": "^2.8.5",
14 | "dotenv": "^16.3.1",
15 | "express": "^4.18.2",
16 | "express-session": "^1.17.3",
17 | "pg": "^8.11.3"
18 | },
19 | "devDependencies": {
20 | "nodemon": "^3.0.2"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/db/setup.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const { pool } = require('./config');
4 |
5 | async function setupDatabase() {
6 | try {
7 | // 读取 init.sql 文件内容
8 | const initSql = fs.readFileSync(path.join(__dirname, 'init.sql'), 'utf8');
9 |
10 | // 连接数据库并执行 SQL
11 | const client = await pool.connect();
12 | try {
13 | await client.query(initSql);
14 | console.log('数据库表创建成功!');
15 | } finally {
16 | client.release();
17 | }
18 | } catch (err) {
19 | console.error('设置数据库时出错:', err);
20 | } finally {
21 | await pool.end();
22 | }
23 | }
24 |
25 | setupDatabase();
26 |
--------------------------------------------------------------------------------
/db/init.sql:
--------------------------------------------------------------------------------
1 | -- 创建用户表
2 | CREATE TABLE IF NOT EXISTS users (
3 | id SERIAL PRIMARY KEY,
4 | username VARCHAR(50) UNIQUE NOT NULL,
5 | password VARCHAR(255) NOT NULL, -- 存储加密后的密码
6 | avatar TEXT, -- 存储头像的base64编码
7 | created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
8 | last_login TIMESTAMP WITH TIME ZONE
9 | );
10 |
11 | -- 创建已完成题目表
12 | CREATE TABLE IF NOT EXISTS completed_problems (
13 | id SERIAL PRIMARY KEY,
14 | user_id INTEGER REFERENCES users(id),
15 | problem_id VARCHAR(50) NOT NULL,
16 | completed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
17 | UNIQUE(user_id, problem_id)
18 | );
19 |
20 | -- 创建消息表
21 | CREATE TABLE IF NOT EXISTS messages (
22 | id SERIAL PRIMARY KEY,
23 | from_user_id INTEGER REFERENCES users(id),
24 | to_user_id INTEGER REFERENCES users(id),
25 | message TEXT NOT NULL,
26 | created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
27 | read BOOLEAN DEFAULT FALSE
28 | );
29 |
--------------------------------------------------------------------------------
/problemSets.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 题单
6 |
7 |
8 |
9 |
10 |
11 |
19 |
20 |
49 |
50 |
--------------------------------------------------------------------------------
/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 关于我们
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 |
关于我们
20 |
欢迎来到我们的在线评测系统!
21 |
22 |
技术栈
23 |
24 | - 前端:HTML, CSS, JavaScript
25 | - 后端:Node.js, Express
26 | - 数据库:MongoDB
27 | - 其他工具:Webpack, Babel
28 |
29 |
30 |
31 |
学习路线图
32 |
33 |
34 |
入门阶段
35 |
36 | - 基础语法
37 | - 简单算法
38 | - 基础数据结构
39 |
40 |
41 |
42 |
进阶阶段
43 |
44 | - 高级算法
45 | - 复杂数据结构
46 | - 算法设计
47 |
48 |
49 |
50 |
专家阶段
51 |
52 | - 竞赛技巧
53 | - 高级优化
54 | - 综合应用
55 |
56 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/services/userService.js:
--------------------------------------------------------------------------------
1 | const db = require('../db/config');
2 | const bcrypt = require('bcrypt');
3 |
4 | const userService = {
5 | // 创建新用户
6 | async createUser(username, password, avatar) {
7 | const hashedPassword = await bcrypt.hash(password, 10);
8 | const result = await db.query(
9 | 'INSERT INTO users (username, password, avatar) VALUES ($1, $2, $3) RETURNING id',
10 | [username, hashedPassword, avatar]
11 | );
12 | return result.rows[0];
13 | },
14 |
15 | // 验证用户登录
16 | async validateUser(username, password) {
17 | const result = await db.query('SELECT * FROM users WHERE username = $1', [username]);
18 | if (result.rows.length === 0) {
19 | return null;
20 | }
21 | const user = result.rows[0];
22 | const validPassword = await bcrypt.compare(password, user.password);
23 | if (!validPassword) {
24 | return null;
25 | }
26 | return user;
27 | },
28 |
29 | // 更新用户最后登录时间
30 | async updateLastLogin(userId) {
31 | await db.query(
32 | 'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = $1',
33 | [userId]
34 | );
35 | },
36 |
37 | // 获取用户信息
38 | async getUserByUsername(username) {
39 | const result = await db.query('SELECT * FROM users WHERE username = $1', [username]);
40 | return result.rows[0];
41 | },
42 |
43 | // 标记题目为已完成
44 | async markProblemAsCompleted(userId, problemId) {
45 | try {
46 | await db.query(
47 | 'INSERT INTO completed_problems (user_id, problem_id) VALUES ($1, $2)',
48 | [userId, problemId]
49 | );
50 | return true;
51 | } catch (error) {
52 | if (error.code === '23505') { // 唯一约束违反
53 | return false;
54 | }
55 | throw error;
56 | }
57 | },
58 |
59 | // 获取用户已完成的题目
60 | async getCompletedProblems(userId) {
61 | const result = await db.query(
62 | 'SELECT problem_id FROM completed_problems WHERE user_id = $1',
63 | [userId]
64 | );
65 | return result.rows.map(row => row.problem_id);
66 | }
67 | };
68 |
69 | module.exports = userService;
70 |
--------------------------------------------------------------------------------
/leaderboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 排行榜
6 |
7 |
8 |
9 |
10 |
11 |
排行榜
12 |
13 |
14 |
15 | | 用户名 |
16 | 完成题目数 |
17 | 操作 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
返回首页
25 |
26 |
27 |
65 |
66 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cors = require('cors');
3 | const session = require('express-session');
4 | const userService = require('./services/userService');
5 |
6 | const app = express();
7 | const port = 3000;
8 |
9 | app.use(cors({
10 | origin: 'http://localhost:5500',
11 | credentials: true
12 | }));
13 |
14 | app.use(express.json({ limit: '50mb' }));
15 | app.use(session({
16 | secret: 'your-secret-key',
17 | resave: false,
18 | saveUninitialized: false,
19 | cookie: {
20 | secure: false, // 开发环境下设为false
21 | maxAge: 7 * 24 * 60 * 60 * 1000 // 7天
22 | }
23 | }));
24 |
25 | // 注册
26 | app.post('/api/register', async (req, res) => {
27 | try {
28 | const { username, password, avatar } = req.body;
29 |
30 | // 检查用户名是否已存在
31 | const existingUser = await userService.getUserByUsername(username);
32 | if (existingUser) {
33 | return res.status(400).json({ error: '用户名已存在' });
34 | }
35 |
36 | const user = await userService.createUser(username, password, avatar);
37 | res.json({ success: true, userId: user.id });
38 | } catch (error) {
39 | console.error('注册错误:', error);
40 | res.status(500).json({ error: '注册失败' });
41 | }
42 | });
43 |
44 | // 登录
45 | app.post('/api/login', async (req, res) => {
46 | try {
47 | const { username, password } = req.body;
48 | const user = await userService.validateUser(username, password);
49 |
50 | if (!user) {
51 | return res.status(401).json({ error: '用户名或密码错误' });
52 | }
53 |
54 | await userService.updateLastLogin(user.id);
55 | req.session.userId = user.id;
56 | req.session.username = user.username;
57 |
58 | res.json({
59 | success: true,
60 | user: {
61 | id: user.id,
62 | username: user.username,
63 | avatar: user.avatar
64 | }
65 | });
66 | } catch (error) {
67 | console.error('登录错误:', error);
68 | res.status(500).json({ error: '登录失败' });
69 | }
70 | });
71 |
72 | // 获取用户信息
73 | app.get('/api/user', async (req, res) => {
74 | if (!req.session.userId) {
75 | return res.status(401).json({ error: '未登录' });
76 | }
77 |
78 | try {
79 | const user = await userService.getUserByUsername(req.session.username);
80 | const completedProblems = await userService.getCompletedProblems(user.id);
81 |
82 | res.json({
83 | id: user.id,
84 | username: user.username,
85 | avatar: user.avatar,
86 | completedProblems
87 | });
88 | } catch (error) {
89 | console.error('获取用户信息错误:', error);
90 | res.status(500).json({ error: '获取用户信息失败' });
91 | }
92 | });
93 |
94 | // 标记题目完成
95 | app.post('/api/problems/complete', async (req, res) => {
96 | if (!req.session.userId) {
97 | return res.status(401).json({ error: '未登录' });
98 | }
99 |
100 | try {
101 | const { problemId } = req.body;
102 | const success = await userService.markProblemAsCompleted(req.session.userId, problemId);
103 | res.json({ success });
104 | } catch (error) {
105 | console.error('标记题目完成错误:', error);
106 | res.status(500).json({ error: '操作失败' });
107 | }
108 | });
109 |
110 | // 退出登录
111 | app.post('/api/logout', (req, res) => {
112 | req.session.destroy();
113 | res.json({ success: true });
114 | });
115 |
116 | app.listen(port, () => {
117 | console.log(`服务器运行在 http://localhost:${port}`);
118 | });
119 |
--------------------------------------------------------------------------------
/userlist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 用户列表
6 |
7 |
8 |
9 |
10 |
11 |
用户列表
12 |
13 |
14 |
15 |
21 |
27 |
返回首页
28 |
29 |
30 |
228 |
229 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: Arial, sans-serif;
4 | background: linear-gradient(135deg, #E3F2FD, #BBDEFB);
5 | background-size: 200% 200%;
6 | animation: gradientAnimation 10s ease infinite;
7 | position: relative;
8 | overflow-x: hidden;
9 | }
10 |
11 | body::before,
12 | body::after {
13 | content: "🌟";
14 | position: fixed;
15 | font-size: 24px;
16 | transition: all 0.5s ease;
17 | opacity: 0.7;
18 | z-index: -1;
19 | }
20 |
21 | body::before {
22 | top: 20px;
23 | left: 20px;
24 | animation: floatTopLeft 3s ease-in-out infinite;
25 | }
26 |
27 | body::after {
28 | bottom: 20px;
29 | right: 20px;
30 | animation: floatBottomRight 3s ease-in-out infinite;
31 | }
32 |
33 | @keyframes gradientAnimation {
34 | 0% { background-position: 0% 50%; }
35 | 50% { background-position: 100% 50%; }
36 | 100% { background-position: 0% 50%; }
37 | }
38 |
39 | @keyframes floatTopLeft {
40 | 0% { transform: translate(0, 0) rotate(0deg); }
41 | 50% { transform: translate(10px, 10px) rotate(180deg); }
42 | 100% { transform: translate(0, 0) rotate(360deg); }
43 | }
44 |
45 | @keyframes floatBottomRight {
46 | 0% { transform: translate(0, 0) rotate(0deg); }
47 | 50% { transform: translate(-10px, -10px) rotate(-180deg); }
48 | 100% { transform: translate(0, 0) rotate(-360deg); }
49 | }
50 |
51 | .container {
52 | max-width: 800px;
53 | margin: 60px auto 0;
54 | padding: 20px;
55 | background-color: rgba(255, 255, 255, 0.95);
56 | border-radius: 8px;
57 | box-shadow: 0 4px 12px rgba(33, 150, 243, 0.15);
58 | position: relative;
59 | z-index: 1;
60 | }
61 |
62 | .container::before,
63 | .container::after {
64 | display: none;
65 | }
66 |
67 | .details {
68 | margin-top: 20px;
69 | }
70 | h5 {
71 | margin: 10px 0;
72 | color: #007bff;
73 | font-weight: bold;
74 | transition: color 0.3s;
75 | }
76 | h5:hover {
77 | color: #0056b3;
78 | }
79 | p, small, pre {
80 | margin: 5px 0;
81 | color: #555;
82 | }
83 | pre {
84 | background-color: #f1f1f1;
85 | padding: 10px;
86 | border-radius: 4px;
87 | overflow-x: auto;
88 | }
89 | button {
90 | padding: 10px 20px;
91 | background-color: #2196F3;
92 | color: white;
93 | border: none;
94 | border-radius: 4px;
95 | cursor: pointer;
96 | transition: background-color 0.3s;
97 | }
98 | button:hover {
99 | background-color: #1976D2;
100 | }
101 | button.active {
102 | background-color: #1565C0;
103 | }
104 | a {
105 | display: inline-block;
106 | margin-top: 20px;
107 | color: #007bff;
108 | text-decoration: none;
109 | transition: color 0.3s;
110 | }
111 | a:hover {
112 | text-decoration: underline;
113 | color: #0056b3;
114 | }
115 | small {
116 | display: block;
117 | margin: 5px 0;
118 | color: #555;
119 | }
120 | .difficulty-easy {
121 | color: #4CAF50;
122 | }
123 | .difficulty-medium {
124 | color: #FF9800;
125 | }
126 | .difficulty-hard {
127 | color: #F44336;
128 | }
129 | .tag {
130 | display: inline-block;
131 | background-color: #e0e0e0;
132 | border-radius: 4px;
133 | padding: 2px 8px;
134 | margin: 2px;
135 | font-size: 0.9em;
136 | }
137 | #pagination {
138 | margin-top: 20px;
139 | text-align: center;
140 | }
141 | .problem {
142 | padding: 10px;
143 | border-bottom: 1px solid #ddd;
144 | }
145 | .about-button {
146 | color: #2196F3;
147 | text-decoration: none;
148 | transition: color 0.3s;
149 | margin-left: 10px;
150 | }
151 | .about-button:hover {
152 | color: #1976D2;
153 | }
154 | .footer {
155 | text-align: center;
156 | margin-top: 40px;
157 | padding-bottom: 20px;
158 | }
159 | input[type="text"], select {
160 | width: calc(100% - 22px);
161 | padding: 10px;
162 | margin-bottom: 10px;
163 | border: 1px solid #ccc;
164 | border-radius: 4px;
165 | box-sizing: border-box;
166 | }
167 | #loading {
168 | text-align: center;
169 | font-size: 16px;
170 | color: #666;
171 | margin-top: 20px;
172 | }
173 | .github-link {
174 | color: #333;
175 | text-decoration: none;
176 | margin-left: 10px;
177 | transition: color 0.3s;
178 | }
179 | .github-link:hover {
180 | color: #007bff;
181 | }
182 | #auth, #loginForm, #registerForm {
183 | margin-bottom: 20px;
184 | text-align: center;
185 | }
186 |
187 | #loginForm input, #registerForm input {
188 | display: block;
189 | margin: 10px auto;
190 | padding: 10px;
191 | width: 80%;
192 | max-width: 300px;
193 | border: 1px solid #ccc;
194 | border-radius: 4px;
195 | }
196 |
197 | #loginForm button, #registerForm button {
198 | margin-top: 10px;
199 | }
200 |
201 | #registerForm input[type="file"] {
202 | margin: 10px auto;
203 | display: block;
204 | }
205 |
206 | img {
207 | display: block;
208 | margin: 20px auto;
209 | width: 100px;
210 | height: 100px;
211 | border-radius: 50%;
212 | border: 2px solid #ccc;
213 | }
214 |
215 | .avatar-container {
216 | position: absolute;
217 | top: 10px;
218 | right: 20px;
219 | display: flex;
220 | align-items: center;
221 | z-index: 3;
222 | margin-top: 50px;
223 | }
224 |
225 | .user-avatar {
226 | width: 50px;
227 | height: 50px;
228 | border-radius: 50%;
229 | border: 2px solid #ccc;
230 | cursor: pointer;
231 | transition: all 0.3s ease;
232 | }
233 |
234 | .user-avatar:hover {
235 | transform: scale(1.1);
236 | border-color: #2196F3;
237 | box-shadow: 0 0 10px rgba(33, 150, 243, 0.3);
238 | }
239 |
240 | .avatar-container::after {
241 | content: "点击进入用户中心";
242 | position: absolute;
243 | bottom: -20px;
244 | right: 0;
245 | font-size: 12px;
246 | color: #666;
247 | opacity: 0;
248 | transition: opacity 0.3s ease;
249 | white-space: nowrap;
250 | }
251 |
252 | .avatar-container:hover::after {
253 | opacity: 1;
254 | }
255 |
256 | .modal {
257 | display: none;
258 | position: fixed;
259 | z-index: 1000;
260 | left: 0;
261 | top: 0;
262 | width: 100%;
263 | height: 100%;
264 | background-color: rgba(0, 0, 0, 0.5);
265 | padding: 0;
266 | box-sizing: border-box;
267 | overflow-y: auto;
268 | }
269 |
270 | .modal-content {
271 | background-color: #fefefe;
272 | margin: 5% auto;
273 | padding: 20px;
274 | border: 1px solid #888;
275 | width: 80%;
276 | max-width: 400px;
277 | border-radius: 8px;
278 | }
279 |
280 | .close {
281 | color: #aaa;
282 | float: right;
283 | font-size: 28px;
284 | font-weight: bold;
285 | cursor: pointer;
286 | margin-left: 15px;
287 | }
288 |
289 | .close:hover,
290 | .close:focus {
291 | color: black;
292 | text-decoration: none;
293 | cursor: pointer;
294 | }
295 |
296 | h1, h2 {
297 | text-align: center;
298 | color: #333;
299 | }
300 |
301 | ul {
302 | list-style-type: none;
303 | padding: 0;
304 | }
305 |
306 | li {
307 | margin: 10px 0;
308 | padding: 10px;
309 | background-color: #f1f1f1;
310 | border-radius: 4px;
311 | }
312 |
313 | .footer {
314 | text-align: center;
315 | margin-top: 20px;
316 | }
317 |
318 | table {
319 | width: 100%;
320 | border-collapse: collapse;
321 | margin-top: 20px;
322 | }
323 |
324 | th, td {
325 | border: 1px solid #ddd;
326 | padding: 8px;
327 | text-align: center;
328 | }
329 |
330 | th {
331 | background-color: #f2f2f2;
332 | font-weight: bold;
333 | }
334 |
335 | .action-button {
336 | background-color: #f0f0f0;
337 | border: none;
338 | border-radius: 4px;
339 | padding: 5px 10px;
340 | margin: 0 5px;
341 | cursor: pointer;
342 | transition: background-color 0.3s;
343 | }
344 |
345 | .action-button:hover {
346 | background-color: #e0e0e0;
347 | }
348 |
349 | .action-button i {
350 | margin-right: 5px;
351 | }
352 | .user-list, .inbox {
353 | margin-top: 20px;
354 | }
355 |
356 | .user-avatar-small {
357 | width: 30px;
358 | height: 30px;
359 | border-radius: 50%;
360 | margin-right: 10px;
361 | vertical-align: middle;
362 | }
363 |
364 | #userList li, #inboxList li {
365 | display: flex;
366 | align-items: center;
367 | justify-content: space-between;
368 | margin: 10px 0;
369 | padding: 10px;
370 | background-color: #f1f1f1;
371 | border-radius: 4px;
372 | }
373 |
374 | #userList button {
375 | margin-left: 10px;
376 | padding: 5px 10px;
377 | background-color: #4CAF50;
378 | color: white;
379 | border: none;
380 | border-radius: 4px;
381 | cursor: pointer;
382 | transition: background-color 0.3s;
383 | }
384 |
385 | #userList button:hover {
386 | background-color: #45a049;
387 | }
388 | .header {
389 | position: absolute;
390 | top: 10px;
391 | left: 10px;
392 | width: 100%;
393 | display: flex;
394 | justify-content: space-between;
395 | align-items: center;
396 | padding: 0 20px 0 0;
397 | box-sizing: border-box;
398 | z-index: 2;
399 | }
400 |
401 | .nav-links {
402 | display: flex;
403 | gap: 20px;
404 | margin-right: 20px;
405 | }
406 |
407 | .nav-button {
408 | display: flex;
409 | align-items: center;
410 | gap: 5px;
411 | padding: 8px 16px;
412 | background-color: #2196F3;
413 | color: white;
414 | border-radius: 4px;
415 | text-decoration: none;
416 | transition: all 0.3s ease;
417 | margin: 0;
418 | }
419 |
420 | .nav-button:hover {
421 | background-color: #1976D2;
422 | transform: translateY(-2px);
423 | text-decoration: none;
424 | }
425 |
426 | .nav-button i {
427 | font-size: 16px;
428 | }
429 |
430 | .mail-icon {
431 | color: #333;
432 | text-decoration: none;
433 | font-size: 24px;
434 | transition: color 0.3s;
435 | position: relative;
436 | z-index: 3;
437 | }
438 |
439 | .mail-icon:hover {
440 | color: #007bff;
441 | }
442 |
443 | .user-profile {
444 | max-width: 600px;
445 | margin: 40px auto;
446 | padding: 20px;
447 | background: white;
448 | border-radius: 8px;
449 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
450 | }
451 |
452 | .back-button {
453 | display: inline-flex;
454 | align-items: center;
455 | gap: 8px;
456 | color: #333;
457 | text-decoration: none;
458 | padding: 8px 16px;
459 | border-radius: 4px;
460 | transition: background-color 0.3s;
461 | }
462 |
463 | .back-button:hover {
464 | background-color: #f0f0f0;
465 | }
466 |
467 | .user-info {
468 | display: flex;
469 | align-items: center;
470 | gap: 10px;
471 | }
472 |
473 | .user-info span {
474 | color: #333;
475 | font-size: 14px;
476 | }
477 |
478 | .top-nav {
479 | position: fixed;
480 | top: 0;
481 | left: 0;
482 | right: 0;
483 | height: 60px;
484 | background-color: white;
485 | box-shadow: 0 2px 4px rgba(0,0,0,0.1);
486 | z-index: 1000;
487 | }
488 |
489 | .top-buttons {
490 | max-width: 1200px;
491 | margin: 0 auto;
492 | height: 100%;
493 | display: flex;
494 | justify-content: space-between;
495 | align-items: center;
496 | padding: 0 20px;
497 | }
498 |
499 | .skip-login {
500 | position: absolute;
501 | top: 20px;
502 | right: 20px;
503 | }
504 |
505 | .skip-login a {
506 | color: #666;
507 | text-decoration: none;
508 | font-size: 14px;
509 | transition: color 0.3s;
510 | }
511 |
512 | .skip-login a:hover {
513 | color: #2196F3;
514 | }
515 |
516 | .auth-buttons {
517 | position: absolute;
518 | top: 20px;
519 | right: 20px;
520 | display: flex;
521 | gap: 10px;
522 | z-index: 2;
523 | }
524 |
525 | .auth-btn {
526 | padding: 8px 16px;
527 | background-color: #2196F3;
528 | color: white;
529 | border: none;
530 | border-radius: 4px;
531 | cursor: pointer;
532 | font-size: 14px;
533 | transition: all 0.3s ease;
534 | }
535 |
536 | .auth-btn:hover {
537 | background-color: #1976D2;
538 | transform: translateY(-1px);
539 | }
540 |
541 | .header {
542 | display: flex;
543 | justify-content: space-between;
544 | align-items: center;
545 | margin-bottom: 30px;
546 | }
--------------------------------------------------------------------------------
/auth.js:
--------------------------------------------------------------------------------
1 | // API 基础URL
2 | const API_BASE_URL = 'http://localhost:3000/api';
3 |
4 | function showLogin() {
5 | document.getElementById('auth').style.display = 'none';
6 | document.getElementById('loginForm').style.display = 'block';
7 | document.getElementById('registerForm').style.display = 'none';
8 | }
9 |
10 | function showRegister() {
11 | document.getElementById('auth').style.display = 'none';
12 | document.getElementById('registerForm').style.display = 'block';
13 | document.getElementById('loginForm').style.display = 'none';
14 | }
15 |
16 | async function register() {
17 | const username = document.getElementById('registerUsername').value;
18 | const password = document.getElementById('registerPassword').value;
19 | const avatar = document.getElementById('registerAvatar').files[0];
20 |
21 | if (!username || !password || !avatar) {
22 | alert('请输入用户名、密码并上传头像。');
23 | return;
24 | }
25 |
26 | try {
27 | // 将头像转换为base64
28 | const base64Avatar = await new Promise((resolve, reject) => {
29 | const reader = new FileReader();
30 | reader.onload = e => resolve(e.target.result);
31 | reader.onerror = reject;
32 | reader.readAsDataURL(avatar);
33 | });
34 |
35 | const response = await fetch(`${API_BASE_URL}/register`, {
36 | method: 'POST',
37 | headers: {
38 | 'Content-Type': 'application/json',
39 | },
40 | body: JSON.stringify({
41 | username,
42 | password,
43 | avatar: base64Avatar
44 | }),
45 | credentials: 'include'
46 | });
47 |
48 | const data = await response.json();
49 | if (response.ok) {
50 | alert('注册成功!');
51 | showLogin();
52 | } else {
53 | alert(data.error || '注册失败');
54 | }
55 | } catch (error) {
56 | console.error('注册错误:', error);
57 | alert('注册失败,请重试');
58 | }
59 | }
60 |
61 | async function login() {
62 | clearErrors();
63 | const username = document.getElementById('loginUsername').value;
64 | const password = document.getElementById('loginPassword').value;
65 |
66 | if (!username || !password) {
67 | !username && showError('loginUsernameError', '请输入用户名');
68 | !password && showError('loginPasswordError', '请输入密码');
69 | return;
70 | }
71 |
72 | try {
73 | const response = await fetch(`${API_BASE_URL}/login`, {
74 | method: 'POST',
75 | headers: {
76 | 'Content-Type': 'application/json',
77 | },
78 | body: JSON.stringify({ username, password }),
79 | credentials: 'include'
80 | });
81 |
82 | const data = await response.json();
83 | if (response.ok) {
84 | window.location.href = 'index.html';
85 | } else {
86 | showError('loginPasswordError', data.error || '登录失败');
87 | }
88 | } catch (error) {
89 | console.error('登录错误:', error);
90 | showError('loginPasswordError', '登录失败,请重试');
91 | }
92 | }
93 |
94 | // 添加等级计算函数
95 | function calculateLevel(completedCount) {
96 | if (completedCount >= 50) return { level: "大师", color: "#ff4757", progress: 100 };
97 | if (completedCount >= 30) return { level: "专家", color: "#ffa502", progress: 80 };
98 | if (completedCount >= 20) return { level: "高手", color: "#2ed573", progress: 60 };
99 | if (completedCount >= 10) return { level: "进阶", color: "#1e90ff", progress: 40 };
100 | if (completedCount >= 5) return { level: "新手", color: "#a4b0be", progress: 20 };
101 | return { level: "初学者", color: "#747d8c", progress: 0 };
102 | }
103 |
104 | // 修改 openUserCenter 函数
105 | function openUserCenter() {
106 | const username = localStorage.getItem('currentUser');
107 | if (!username) return;
108 |
109 | const userData = JSON.parse(localStorage.getItem(username));
110 | document.getElementById('userCenter').style.display = 'block';
111 |
112 | // 计算成就和等级
113 | const completedCount = userData.completedProblems ? userData.completedProblems.length : 0;
114 | const levelInfo = calculateLevel(completedCount);
115 | const nextLevel = getNextLevel(completedCount);
116 |
117 | // 更新用户中心内容
118 | document.querySelector('.modal-content').innerHTML = `
119 | ×
120 |
121 |
122 | ${(() => {
123 | const title = calculateTitle(userData);
124 | return `
125 |
126 | ${title.icon} ${title.name}
127 |
128 | `;
129 | })()}
130 |
131 |
132 |

133 |
134 |
137 |
138 |
139 |
140 |
141 |
用户名: ${username}
142 |
143 |
144 |
145 |
146 | 今日已修改过用户名,请明天再试
147 |
148 |
149 |
150 |
151 | 完成题目数: ${completedCount}
152 |
153 |
154 |
修改密码
155 |
156 |
157 |
158 |
159 |
162 |
163 |
164 | `;
165 | }
166 |
167 | function closeUserCenter() {
168 | document.getElementById('userCenter').style.display = 'none';
169 | }
170 |
171 | function changePassword() {
172 | const newPassword = document.getElementById('newPassword').value;
173 | const username = localStorage.getItem('currentUser');
174 | const userData = JSON.parse(localStorage.getItem(username));
175 |
176 | if (newPassword) {
177 | userData.password = newPassword;
178 | localStorage.setItem(username, JSON.stringify(userData));
179 | alert('密码修改成功!');
180 | closeUserCenter();
181 | } else {
182 | alert('请输入新密码。');
183 | }
184 | }
185 |
186 | function markAsDone(problemId) {
187 | const username = localStorage.getItem('currentUser');
188 | if (!username) {
189 | alert('请先登录!');
190 | return;
191 | }
192 |
193 | const userData = JSON.parse(localStorage.getItem(username));
194 | if (!userData.completedProblems) {
195 | userData.completedProblems = [];
196 | }
197 |
198 | if (!userData.completedProblems.includes(problemId)) {
199 | userData.completedProblems.push(problemId);
200 | localStorage.setItem(username, JSON.stringify(userData));
201 | alert('题目已标记为完成!');
202 | } else {
203 | const problemTitle = problems.find(p => p.id === problemId)?.title || problemId;
204 | alert(`您已经完成过 "${problemTitle}" 这道题目了!`);
205 | }
206 |
207 | updateProgress();
208 | updateDailyStats();
209 | }
210 |
211 | // 获取下一个等级信息
212 | function getNextLevel(completedCount) {
213 | if (completedCount >= 50) return null;
214 | if (completedCount >= 30) return { level: "大师", required: 50 };
215 | if (completedCount >= 20) return { level: "专家", required: 30 };
216 | if (completedCount >= 10) return { level: "高手", required: 20 };
217 | if (completedCount >= 5) return { level: "进阶", required: 10 };
218 | return { level: "新手", required: 5 };
219 | }
220 |
221 | async function checkLoginStatus() {
222 | try {
223 | const response = await fetch(`${API_BASE_URL}/user`, {
224 | credentials: 'include'
225 | });
226 |
227 | if (response.ok) {
228 | const userData = await response.json();
229 | const authButtons = document.getElementById('authButtons');
230 | const avatarContainer = document.getElementById('avatarContainer');
231 |
232 | // 隐藏登录注册按钮
233 | if (authButtons) {
234 | authButtons.style.display = 'none';
235 | }
236 |
237 | // 显示头像
238 | if (avatarContainer) {
239 | avatarContainer.innerHTML = '';
240 | const avatarImg = document.createElement('img');
241 | avatarImg.src = userData.avatar;
242 | avatarImg.alt = '用户头像';
243 | avatarImg.className = 'user-avatar';
244 | avatarImg.onclick = () => window.location.href = 'userCenter.html';
245 | avatarContainer.appendChild(avatarImg);
246 | avatarContainer.style.display = 'flex';
247 | }
248 |
249 | // 更新欢迎信息
250 | const welcomeUsername = document.getElementById('username');
251 | if (welcomeUsername) {
252 | welcomeUsername.textContent = userData.username;
253 | }
254 |
255 | return userData;
256 | } else {
257 | // 显示登录注册按钮
258 | const authButtons = document.getElementById('authButtons');
259 | if (authButtons) {
260 | authButtons.style.display = 'flex';
261 | }
262 |
263 | // 隐藏头像
264 | const avatarContainer = document.getElementById('avatarContainer');
265 | if (avatarContainer) {
266 | avatarContainer.style.display = 'none';
267 | }
268 |
269 | // 更新欢迎信息为游客
270 | const welcomeUsername = document.getElementById('username');
271 | if (welcomeUsername) {
272 | welcomeUsername.textContent = '游客';
273 | }
274 |
275 | return null;
276 | }
277 | } catch (error) {
278 | console.error('检查登录状态错误:', error);
279 | return null;
280 | }
281 | }
282 |
283 | async function logout() {
284 | try {
285 | await fetch(`${API_BASE_URL}/logout`, {
286 | method: 'POST',
287 | credentials: 'include'
288 | });
289 | window.location.href = 'index.html';
290 | } catch (error) {
291 | console.error('退出登录错误:', error);
292 | alert('退出登录失败,请重试');
293 | }
294 | }
295 |
296 | // 在页面加载时检查登录状态
297 | document.addEventListener('DOMContentLoaded', checkLoginStatus);
298 |
299 | document.addEventListener('DOMContentLoaded', function() {
300 | // 登录表单提交事件
301 | const loginForm = document.getElementById('loginForm');
302 | if (loginForm) {
303 | loginForm.addEventListener('submit', function(e) {
304 | e.preventDefault();
305 | login();
306 | });
307 | }
308 |
309 | // 注册表单提交事件
310 | const registerForm = document.getElementById('registerForm');
311 | if (registerForm) {
312 | registerForm.addEventListener('submit', function(e) {
313 | e.preventDefault();
314 | register();
315 | });
316 | }
317 |
318 | // 切换到登录表单按钮
319 | const showLoginBtn = document.querySelector('.show-login');
320 | if (showLoginBtn) {
321 | showLoginBtn.addEventListener('click', function() {
322 | showLogin();
323 | });
324 | }
325 |
326 | // 切换到注册表单按钮
327 | const showRegisterBtn = document.querySelector('.show-register');
328 | if (showRegisterBtn) {
329 | showRegisterBtn.addEventListener('click', function() {
330 | showRegister();
331 | });
332 | }
333 | });
334 |
335 | function updateWelcomeInfo() {
336 | const welcomeUsername = document.getElementById('welcomeUsername');
337 | if (!welcomeUsername) return; // 如果元素不存在则直接返回
338 |
339 | const currentUser = localStorage.getItem('currentUser');
340 | if (currentUser) {
341 | welcomeUsername.textContent = currentUser;
342 | } else {
343 | welcomeUsername.textContent = '游客';
344 | }
345 | }
346 |
347 | // 同时我们需要添加这些相关函数
348 | function updateCalendar() {
349 | // 可以先留空,或者添加日历更新逻辑
350 | }
351 |
352 | function updateDailyQuote() {
353 | // 可以先留空,或者添加每日名言更新逻辑
354 | }
355 |
356 | function updateDailyStats() {
357 | // 可以先留空,或者添加每日统计更新逻辑
358 | }
359 |
360 | // 在页面加载时检查 URL hash
361 | window.addEventListener('load', function() {
362 | if (window.location.hash === '#login') {
363 | showLogin();
364 | } else if (window.location.hash === '#register') {
365 | showRegister();
366 | }
367 | });
--------------------------------------------------------------------------------
/problems.js:
--------------------------------------------------------------------------------
1 | const problems = [
2 | {
3 | id: 1,
4 | title: "A + B Problem",
5 | description: "给定两个整数A和B,输出A+B的和。",
6 | input: "输入包含两个整数A和B,用空格分隔。",
7 | output: "输出一个整数,表示A+B的和。",
8 | sample_input: "1 2",
9 | sample_output: "3",
10 | timeLimit: "1000ms",
11 | memoryLimit: "256MB",
12 | difficulty: "简单",
13 | tags: ["数学", "入门"]
14 | },
15 | {
16 | id: 2,
17 | title: "最大公约数",
18 | description: "给定两个整数,求它们的最大公约数。",
19 | input: "输入包含两个整数。",
20 | output: "输出这两个整数的最大公约数。",
21 | sample_input: "12 18",
22 | sample_output: "6",
23 | difficulty: "中等",
24 | tags: ["数学", "算法"]
25 | },
26 | {
27 | id: 3,
28 | title: "斐波那契数列",
29 | description: "计算斐波那契数列的第N项。",
30 | input: "输入一个整数N。",
31 | output: "输出斐波那契数列的第N项。",
32 | sample_input: "5",
33 | sample_output: "5",
34 | difficulty: "简单",
35 | tags: ["递推", "动态规划"]
36 | },
37 | {
38 | id: 4,
39 | title: "阶乘计算",
40 | description: "计算给定整数的阶乘。",
41 | input: "输入一个整数N。",
42 | output: "输出N的阶乘。",
43 | sample_input: "4",
44 | sample_output: "24",
45 | difficulty: "简单",
46 | tags: ["递推", "数学"]
47 | },
48 | {
49 | id: 5,
50 | title: "爬楼梯问题",
51 | description: "计算爬楼梯的不同方法数。",
52 | input: "输入一个整数N,表示楼梯的阶数。",
53 | output: "输出爬到第N阶的方法数。",
54 | sample_input: "3",
55 | sample_output: "3",
56 | difficulty: "中等",
57 | tags: ["递推", "动态规划"]
58 | },
59 | {
60 | id: 6,
61 | title: "汉诺塔问题",
62 | description: "计算汉诺塔问题的最小移动次数。",
63 | input: "输入一个整数N,表示盘子的数量。",
64 | output: "输出最小移动次数。",
65 | sample_input: "3",
66 | sample_output: "7",
67 | difficulty: "中等",
68 | tags: ["递推", "算法"]
69 | },
70 | {
71 | id: 7,
72 | title: "斐波那契数列(优化)",
73 | description: "使用优化算法计算斐波那契数列的第N项。",
74 | input: "输入一个整数N。",
75 | output: "输出斐波那契数列的第N项。",
76 | sample_input: "10",
77 | sample_output: "55",
78 | difficulty: "中等",
79 | tags: ["递推", "动态规划"]
80 | },
81 | {
82 | id: 8,
83 | title: "二项式系数",
84 | description: "计算二项式系数C(n, k)。",
85 | input: "输入两个整数n和k。",
86 | output: "输出C(n, k)。",
87 | sample_input: "5 2",
88 | sample_output: "10",
89 | difficulty: "中等",
90 | tags: ["递推", "数学"]
91 | },
92 | {
93 | id: 9,
94 | title: "最长递增子序列",
95 | description: "计算数组的最长递增子序列的长度。",
96 | input: "输入一个整数数组。",
97 | output: "输出最长递增子序列的长度。",
98 | sample_input: "10 9 2 5 3 7 101 18",
99 | sample_output: "4",
100 | difficulty: "困难",
101 | tags: ["递推", "动态规划"]
102 | },
103 | {
104 | id: 10,
105 | title: "硬币问题",
106 | description: "计算用给定面值的硬币凑成目标金额的最少硬币数。",
107 | input: "输入一个整数数组表示硬币面值和一个整数表示目标金额。",
108 | output: "输出最少硬币数。",
109 | sample_input: "1 2 5 11",
110 | sample_output: "3",
111 | difficulty: "困难",
112 | tags: ["递推", "动态规划"]
113 | },
114 | {
115 | id: 11,
116 | title: "编辑距离",
117 | description: "计算两个字符串的编辑距离。",
118 | input: "输入两个字符串。",
119 | output: "输出编辑距离。",
120 | sample_input: "horse ros",
121 | sample_output: "3",
122 | difficulty: "困难",
123 | tags: ["递推", "动态规划"]
124 | },
125 | {
126 | id: 12,
127 | title: "快速排序",
128 | description: "实现快速排序算法。",
129 | input: "一个整数数组。",
130 | output: "排序后的数组。",
131 | sample_input: "5 2 9 1 7 6 3",
132 | sample_output: "1 2 3 5 6 7 9",
133 | difficulty: "中等",
134 | tags: ["算法", "排序"]
135 | },
136 | {
137 | id: 13,
138 | title: "二叉树遍历",
139 | description: "实现二叉树的前序、中序和后序遍历。",
140 | input: "一个二叉树。",
141 | output: "三种遍历序列。",
142 | sample_input: "1 2 3 4 5 null 6",
143 | sample_output: "前序:1 2 4 5 3 6\n中序:4 2 5 1 3 6\n后序:4 5 2 6 3 1",
144 | difficulty: "中等",
145 | tags: ["数据结构", "树"]
146 | },
147 | {
148 | id: 14,
149 | title: "最短路径",
150 | description: "使用Dijkstra算法求解最短路径问题。",
151 | input: "图的邻接矩阵和起点。",
152 | output: "从起点到所有点的最短距离。",
153 | sample_input: "4\n0 2 4 ∞\n2 0 1 3\n4 1 0 1\n∞ 3 1 0\n0",
154 | sample_output: "0 2 3 4",
155 | difficulty: "困难",
156 | tags: ["算法", "图论"]
157 | },
158 | {
159 | id: 15,
160 | title: "背包问题",
161 | description: "解决0-1背包问题。",
162 | input: "物品的重量和价值,背包容量。",
163 | output: "最大价值。",
164 | sample_input: "N=3, W=4\n2 3\n1 2\n3 4",
165 | sample_output: "6",
166 | difficulty: "中等",
167 | tags: ["动态规划", "算法"]
168 | },
169 | {
170 | id: 16,
171 | title: "平衡二叉树",
172 | description: "实现AVL树的插入和删除操作。",
173 | input: "一系列操作指令。",
174 | output: "操作后的树结构。",
175 | sample_input: "insert 3\ninsert 1\ninsert 4\ndelete 1",
176 | sample_output: "3\n 4",
177 | difficulty: "困难",
178 | tags: ["数据结构", "树"]
179 | },
180 | {
181 | id: 17,
182 | title: "矩阵快速幂",
183 | description: "计算矩阵的n次幂。",
184 | input: "一个矩阵和幂次n。",
185 | output: "结果矩阵。",
186 | sample_input: "2 2\n1 1\n1 0\n2",
187 | sample_output: "2 1\n1 1",
188 | difficulty: "中等",
189 | tags: ["数学", "矩阵"]
190 | },
191 | {
192 | id: 18,
193 | title: "线段树",
194 | description: "实现线段树的区间查询和修改。",
195 | input: "数组和操作序列。",
196 | output: "查询结果。",
197 | sample_input: "1 3 5 7 9 11\nquery 1 3\nupdate 2 4\nquery 1 3",
198 | sample_output: "15\n12",
199 | difficulty: "困难",
200 | tags: ["数据结构", "树"]
201 | },
202 | {
203 | id: 19,
204 | title: "最长回文子串",
205 | description: "找出字符串中最长的回文子串。",
206 | input: "一个字符串。",
207 | output: "最长回文子串。",
208 | sample_input: "babad",
209 | sample_output: "bab",
210 | difficulty: "中等",
211 | tags: ["字符串", "动态规划"]
212 | },
213 | {
214 | id: 20,
215 | title: "并查集",
216 | description: "实现并查集的合并和查找操作。",
217 | input: "元素集合和操作序列。",
218 | output: "操作结果。",
219 | sample_input: "5\nunion 1 2\nunion 3 4\nfind 1 4",
220 | sample_output: "false",
221 | difficulty: "中等",
222 | tags: ["数据结构", "图论"]
223 | },
224 | {
225 | id: 21,
226 | title: "红黑树",
227 | description: "实现红黑树的插入和删除操作。",
228 | input: "操作序列。",
229 | output: "树的状态。",
230 | sample_input: "insert 5\ninsert 3\ninsert 7\ndelete 3",
231 | sample_output: "5(B)\n 7(R)",
232 | difficulty: "困难",
233 | tags: ["数据结构", "树"]
234 | },
235 | {
236 | id: 22,
237 | title: "数组区间和",
238 | description: "给定一个整数数组和多个查询区间,求每个区间内的元素和。",
239 | input: "第一行输入数组长度n和查询次数q,第二行输入n个整数,接下来q行每行输入两个整数l,r表示查询区间。",
240 | output: "输出q行,每行一个整数表示对应区间的元素和。",
241 | sample_input: "5 2\n1 2 3 4 5\n1 3\n2 4",
242 | sample_output: "6\n9",
243 | difficulty: "简单",
244 | tags: ["数组", "前缀和"]
245 | },
246 | {
247 | id: 23,
248 | title: "最大子数组和",
249 | description: "给定一个整数数组,找到一个具有最大和的连续子数组。",
250 | input: "第一行输入数组长度n,第二行输入n个整数。",
251 | output: "输出最大子数组和。",
252 | sample_input: "9\n-2 1 -3 4 -1 2 1 -5 4",
253 | sample_output: "6",
254 | difficulty: "中等",
255 | tags: ["数组", "动态规划"]
256 | },
257 | {
258 | id: 24,
259 | title: "杨辉三角",
260 | description: "生成杨辉三角的前n行。",
261 | input: "一个整数n。",
262 | output: "杨辉三角的前n行。",
263 | sample_input: "5",
264 | sample_output: "1\n1 1\n1 2 1\n1 3 3 1\n1 4 6 4 1",
265 | difficulty: "简单",
266 | tags: ["递推", "数组"]
267 | },
268 | {
269 | id: 25,
270 | title: "不同路径",
271 | description: "一个机器人位于m×n网格的左上角,每次只能向下或向右移动一步,求达到右下角的不同路径数。",
272 | input: "两个整数m和n。",
273 | output: "不同路径的数量。",
274 | sample_input: "3 7",
275 | sample_output: "28",
276 | difficulty: "中等",
277 | tags: ["动态规划", "递推"]
278 | },
279 | {
280 | id: 26,
281 | title: "最长公共子序列",
282 | description: "给定两个字符串,求它们的最长公共子序列的长度。",
283 | input: "两行,每行一个字符串。",
284 | output: "最长公共子序列的长度。",
285 | sample_input: "abcde\nace",
286 | sample_output: "3",
287 | difficulty: "中等",
288 | tags: ["动态规划", "字符串"]
289 | },
290 | {
291 | id: 27,
292 | title: "昆虫繁殖",
293 | description: "计算昆虫在n天后的数量。每只昆虫每天可以繁殖出一只新的昆虫,新昆虫第二天开始也能繁殖。",
294 | input: "一个整数n,表示天数。",
295 | output: "第n天时昆虫的总数。",
296 | sample_input: "3",
297 | sample_output: "4",
298 | difficulty: "简单",
299 | tags: ["递推", "数学"]
300 | },
301 | {
302 | id: 28,
303 | title: "位数问题",
304 | description: "给定一个整数n,求1到n中所有整数的位数之和。",
305 | input: "一个整数n。",
306 | output: "1到n中所有整数的位数之和。",
307 | sample_input: "13",
308 | sample_output: "17",
309 | difficulty: "中等",
310 | tags: ["数学", "递推"]
311 | },
312 | {
313 | id: 29,
314 | title: "过河卒(Noip2002)",
315 | description: "棋盘上A点(0,0)有一个过河卒,需要走到目标B点(n,m)。卒只能向下或向右走。棋盘上某一点有一个对方的马,该马所在的点和所有跳跃一步可达的点都称为马的控制点,卒不能通过这些控制点。已知B点坐标(n,m)(n,m不超过20)和马的位置坐标(x,y)(不与起点终点重合),求卒从A点到B点的可行路径数。",
316 | input: "一行四个整数n、m、x、y,分别表示B点坐标(n,m)和马的位置(x,y)。",
317 | output: "可行路径数。",
318 | sample_input: "8 6 0 4",
319 | sample_output: "1617",
320 | difficulty: "中等",
321 | tags: ["动态规划", "递推"]
322 | },
323 | {
324 | id: 30,
325 | title: "斐波那契数列(2)",
326 | description: "计算斐波那契数列的第n项对m取模的结果。",
327 | input: "两个整数n和m。",
328 | output: "第n项对m取模的结果。",
329 | sample_input: "10 7",
330 | sample_output: "4",
331 | difficulty: "中等",
332 | tags: ["数学", "递推"]
333 | },
334 | {
335 | id: 31,
336 | title: "Pell数列",
337 | description: "Pell数列满足P(n)=2*P(n-1)+P(n-2),初始值P(1)=1, P(2)=2。",
338 | input: "一个整数n。",
339 | output: "Pell数列的第n项。",
340 | sample_input: "5",
341 | sample_output: "29",
342 | difficulty: "中等",
343 | tags: ["数学", "递推"]
344 | },
345 | {
346 | id: 32,
347 | title: "CSP-J/S 2024 第二轮 入门级 扑克牌",
348 | description: `小 P 从同学小 Q 那儿借来一副 n 张牌的扑克牌。本题中我们不考虑大小王,此时每张牌具有两个属性:花色和点数。花色共有 4 种:方片(D)、草花(C)、红桃(H)和黑桃(S)。点数共有 13 种,从小到大分别为 A 2 3 4 5 6 7 8 9 T J Q K。注意:点数 10 在本题中记为 T。`,
349 | difficulty: "简单",
350 | tags: ["模拟", "计数"],
351 | input: "从文件 poker.in 中读入数据。第一行包含一个整数 n 表示牌数。接下来 n 行每行包含一个长度为 2 的字符串描述一张牌。",
352 | output: "输出到文件 poker.out 中。输出一行一个整数,表示最少还需要向小 S 借几张牌才能凑成一副完整的扑克牌。",
353 | sample_input: "1\nSA",
354 | sample_output: "51",
355 | timeLimit: "1.0秒",
356 | memoryLimit: "512 MiB"
357 | },
358 | {
359 | id: 33,
360 | title: "CSP-J/S 2024 第二轮 入门级 地图探险",
361 | description: `小 A 打算前往一片丛林去探险。丛林的地图可以用一个 n 行 m 列的字符表来表示。机器人的状态由位置和朝向两部分组成。其中位置由坐标 (x, y) 刻画,朝向用一个 0~3 的整数 d 表示,其中 d = 0 代表向东,d = 1 代表向南,d = 2 代表向西,d = 3 代表向北。`,
362 | difficulty: "中等",
363 | tags: ["模拟", "方向控制"],
364 | input: "从文件 explore.in 中读入数据。第一行包含三个正整数 n,m,k。第二行包含两个正整数 x0,y0 和一个非负整数 d0。",
365 | output: "输出到文件 explore.out 中。对于每组数据输出一行包含一个正整数,表示地图上所有被机器人经过的位置的个数。",
366 | sample_input: "2\n5 4\n1 1 2\n....x",
367 | sample_output: "3",
368 | timeLimit: "1.0秒",
369 | memoryLimit: "512 MiB"
370 | },
371 | {
372 | id: 34,
373 | title: "CSP-J/S 2024 第二轮 入门级 小木棍",
374 | description: `小 S 喜欢收集小木棍。在收集了 n 根长度相等的小木棍之后,他闲来无事,便用它们拼起了数字。现在小 S 希望拼出一个正整数,满足如下条件:拼出这个数恰好使用 n 根小木棍;拼出的数没有前导 0;在满足以上两个条件的前提下,这个数尽可能小。`,
375 | difficulty: "中等",
376 | tags: ["贪心", "数字构造"],
377 | input: "从文件 sticks.in 中读入数据。第一行包含一个正整数 T,表示数据组数。接下来包含 T 组数据,每组数据一行包含一个整数 n。",
378 | output: "输出到文件 sticks.out 中。对于每组数据输出一行,如果存在满足题意的正整数,输出这个数;否则输出 -1。",
379 | sample_input: "5\n1\n2\n3\n6\n18",
380 | sample_output: "-1\n1\n7\n6\n208",
381 | timeLimit: "1.0秒",
382 | memoryLimit: "512 MiB"
383 | },
384 | {
385 | id: 35,
386 | title: "CSP-J/S 2024 第二轮 入门级 接龙",
387 | description: `在玩惯了成语接龙之后,小 J 和他的朋友们发明了一个新的接龙规则。总共有 n 个人参与这个接龙游戏,第 i 个人会获得一个整数序列 Si 作为他的词库。一次游戏分为若干轮,每一轮有一个人带着词库进行接龙。`,
388 | difficulty: "困难",
389 | tags: ["动态规划", "序列"],
390 | input: "从文件 chain.in 中读入数据。第一行包含三个整数 n,k,q。接下来 n 行,第 i 行包含 (li+1) 个整数。接下来 q 行,每行包含两个整数 rj,cj。",
391 | output: "输出到文件 chain.out 中。对于每个任务输出一行包含一个整数,若任务可以完成输出 1,否则输出 0。",
392 | sample_input: "3 3 7\n5 1 2 3 4 1\n3 1 2 5\n3 5 1 6\n1 2\n1 4\n2 4\n3 4\n6 6\n1 1\n7 7",
393 | sample_output: "1\n0\n1\n0\n1\n0\n0",
394 | timeLimit: "2.0秒",
395 | memoryLimit: "512 MiB"
396 | }
397 | ];
398 |
399 | const problemSets = {
400 | "CSP-J2 2024入门组": {
401 | description: "CSP-J/S 2024 第二轮认证 入门级",
402 | problems: [32, 33, 34, 35],
403 | date: "2024-10-26",
404 | time: "08:30 ~ 12:00",
405 | details: {
406 | timeLimit: {
407 | poker: "1.0秒",
408 | explore: "1.0秒",
409 | sticks: "1.0秒",
410 | chain: "2.0秒"
411 | },
412 | memoryLimit: "512 MiB",
413 | compiler: {
414 | "C++": "-O2 -std=c++14 -static"
415 | },
416 | notes: [
417 | "文件名(程序名和输入输出文件名)必须使用英文小写",
418 | "main 函数的返回值类型必须是 int,程序正常结束时的返回值必须是 0",
419 | "提交的程序代码文件的放置位置请参考各省的具体要求",
420 | "若无特殊说明,结果的比较方式为全文比较(过滤行末空格及文末回车)",
421 | "选手提交的程序源文件必须不大于 100KB",
422 | "程序可使用的栈空间内存限制与题目的内存限制一致"
423 | ]
424 | }
425 | }
426 | };
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 题库
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
27 |
35 |
36 |
欢迎回来,游客
37 |
38 |
39 | 十二月
40 | 06
41 | 星期五
42 |
43 |
44 |
距 CSP-J/S 2025 第一轮 还剩 287 天
45 |
距 CSP-J/S 2025 第二轮 还剩 322 天
46 |
47 |
48 |
49 |
50 |
57 |
65 |
66 |
67 |
68 |
74 |
80 |
81 |
82 |
86 |
87 |
88 |
94 |
加载中...
95 |
96 |
97 |
×
98 |
用户中心
99 |
100 |
![当前头像]()
101 |
102 |
105 |
106 |
107 |
108 |
109 |
用户名:
110 |
111 |
112 |
113 |
114 | 今日已修改过用户名,请明天再试
115 |
116 |
117 |
118 |
已完成题目数: 0
119 |
修改密码
120 |
121 |
122 |
145 |
146 |
学习目标
147 |
148 |
149 |
150 |
151 |
152 |
本周已完成: 0/0
153 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | 编程不仅是代码,更是一种思维方式。
165 |
166 |
167 |
——Linus Torvalds
168 |
169 |
170 |
171 | 今日完成
172 | 0
173 | 题
174 |
175 |
176 | 连续打卡
177 | 0
178 | 天
179 |
180 |
181 |
182 |
183 |
184 |
384 |
385 |
386 |
--------------------------------------------------------------------------------
/problem.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 题目详情
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
46 |
47 |
48 |
49 |
51 |
52 |
53 |
54 |
测试用例
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
请等待10分钟后再尝试。
74 |
77 |
78 |
79 |
457 |
458 |
--------------------------------------------------------------------------------